#javascript #html #angular #typescript
#javascript #HTML #угловатый #машинописный текст
Вопрос:
Поскольку я новичок в Angular, мне нужна ваша помощь. Я просмотрел много руководств и перепробовал много вещей, но на этот раз мне нужно объединить функцию JavaScript с моим компонентом Angular.
Это фактический слайдер, который я хочу использовать внутри Angular (большое спасибо SomoKRoceS):
document.getElementsByTagName("body")[0].onload = createAnimation;
function createAnimation(){
let e = document.getElementById("logo-gallery"); // Get the element
var style = document.createElement('style'); // Create styling element
style.type = 'text/css'; // Append a css type
// Now create the dynamic keyFrames (that are depend on logo-gallery final width)
// Notice that the width of e is given to translateX at 100%
let keyframes = '
@keyframes scroll-left {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-' e.scrollWidth 'px);
}
}';
style.innerHTML = keyframes; // Set innerHTML of the styling element to the keyframe
document.getElementsByTagName('head')[0].appendChild(style); // append the element to the head of the document as a stylesheet
e.setAttribute("style","animation: scroll-left 20s linear infinite; animation-iteration-count: infinite;"); // Give the element its animation properties.
}
#logo-gallery-wrapper {
overflow: hidden;
position: relative;
}
#logo-gallery {
margin: 0;
padding: 0;
position: relative;
list-style-type: none;
display: flex;
}
#logo-gallery .logo-gallery-figure {
margin: 0;
padding: 0 1.6rem;
overflow: hidden;
}
#logo-gallery .logo-gallery-figure img {
height: auto;
max-height: 50px;
position: relative;
filter: grayscale(1);
transition: all .4s;
}
#logo-gallery .logo-gallery-figure img:hover {
filter: grayscale(0);
}
<div id="logo-gallery-wrapper">
<ul id="logo-gallery">
<li>
<figure class="logo-gallery-figure">
<img src="https://www.ikea.com/de/de/static/ikea-logo.f88b07ceb5a8c356b7a0fdcc9a563d63.svg">
</figure>
</li>
<li>
<figure class="logo-gallery-figure">
<img src="https://www.ikea.com/de/de/static/ikea-logo.f88b07ceb5a8c356b7a0fdcc9a563d63.svg">
</figure>
</li>
<li>
<figure class="logo-gallery-figure">
<img src="https://www.ikea.com/de/de/static/ikea-logo.f88b07ceb5a8c356b7a0fdcc9a563d63.svg">
</figure>
</li>
<li>
<figure class="logo-gallery-figure">
<img src="https://www.ikea.com/de/de/static/ikea-logo.f88b07ceb5a8c356b7a0fdcc9a563d63.svg">
</figure>
</li>
<li>
<figure class="logo-gallery-figure">
<img src="https://www.ikea.com/de/de/static/ikea-logo.f88b07ceb5a8c356b7a0fdcc9a563d63.svg">
</figure>
</li>
<li>
<figure class="logo-gallery-figure">
<img src="https://www.ikea.com/de/de/static/ikea-logo.f88b07ceb5a8c356b7a0fdcc9a563d63.svg">
</figure>
</li>
<li>
<figure class="logo-gallery-figure">
<img src="https://www.ikea.com/de/de/static/ikea-logo.f88b07ceb5a8c356b7a0fdcc9a563d63.svg">
</figure>
</li>
</ul>
</div>
На самом деле у меня есть свой компонент с файлами .css, .html и .ts. Содержимое файла HTML и CSS относится к содержимому здесь, но теперь мне каким-то образом нужно преобразовать JavaScript в Angular. Это мой файл .ts:
import { Component, Input } from '@angular/core';
@Component({
selector : 'app-logo-gallery',
templateUrl: './logo-gallery.component.html',
styleUrls : ['./logo-gallery.component.css']
})
export class LogoGalleryComponent {
@Input()
logos: string[];
constructor() {
}
}
Я знаю, что в @Component decorator есть команда анимации, доступная, но правильно ли это, и если да, то как я могу ее там использовать? Дело в том, что мне нужно каким-то образом вычислить ширину прокрутки и установить ее на ключевой кадр, иначе анимация была бы ошибочной.
Я уже знаю, что таким образом я могу получить ширину элемента:
HTML
<ul id="logo-gallery" #logoGallery>
Класс TypeScript
@ViewChild('logoGallery', {static: false}) logoGallery: ElementRef;
this.logoGallery.nativeElement.offsetWidth
Спасибо за вашу помощь!
Ответ №1:
Другой подход — использовать угловые анимации. Единственное, что вам нужно учитывать, это использовать @ViewChildren
для получения «HTMLElement». Мне не нравится писать несколько, если я могу использовать *ngFor , поэтому я использую массив и пишу один li и другой, используя массив. Некоторые из них
Некоторые из них
<div #wrapper id="logo-gallery-wrapper">
<ul #banner id="logo-gallery" >
<li #logo>
<figure class="logo-gallery-figure">
<img (load)="resize()" src="https://www.ikea.com/de/de/static/ikea-logo.f88b07ceb5a8c356b7a0fdcc9a563d63.svg">
</figure>
</li>
<li *ngFor="let i of items;let first=first">
<figure class="logo-gallery-figure">
<img src="https://www.ikea.com/de/de/static/ikea-logo.f88b07ceb5a8c356b7a0fdcc9a563d63.svg">
</figure>
</li>
</ul>
</div>
Посмотрите, что первое «изображение» имеет событие «load», это событие, которое позволяет мне вычислить, сколько элементов нужно, и запустить анимацию.
logoWidth: number = 0;
items: number[] = [0];
public player: AnimationPlayer;
@ViewChild("wrapper") wrapper: ElementRef;
@ViewChild("logo") logo: ElementRef;
@ViewChild("banner") banner: ElementRef;
constructor(private builder: AnimationBuilder) {}
resize() {
this.logoWidth = this.logo.nativeElement.getBoundingClientRect().width;
this.createAnimation();
}
createAnimation() {
if (this.wrapper amp;amp; this.logo) {
//width of the "wrapper"
const totalWidth = this.wrapper.nativeElement.getBoundingClientRect()
.width;
//number of element I go to paint
const element = 2 * Math.floor(totalWidth / this.logoWidth);
//I go to translate the "half" of the "ul"
const inc = this.logoWidth * (element / 2);
//I recalculate the number of elements if you resize the window
if (this.items.length != element)
this.items = new Array(element > 0 ? element : 1);
// the time spend in the animation is proportional to the "total width"
const time = 9.2 * totalWidth "ms";
//create a manual animation
const myAnimation: AnimationFactory = this.builder.build([
style({ transform: `translateX(0px)` }),
animate(time, style({ transform: `translateX(${-inc}px)` }))
]);
this.player = myAnimation.create(this.banner.nativeElement);
//when finish repeat process
this.player.onDone(() => {
this.createAnimation();
});
//finally lauch the animation
this.player.play();
}
}
вы можете видеть в этом стекблите будьте осторожны! если вы измените код, вам нужно обновить .html, чтобы выполнить анимацию раньше, иначе вы можете привести к сбою вашего навигатора-
И дополнительным преимуществом является то, что вы можете приостановить / запустить анимацию с помощью наведения курсора мыши и наведения курсора мыши
<ul (mouseover)="player.pause()"
(mouseout)="player.play()"
#banner id="logo-gallery" >
...
</ul>
Обновление что произойдет, если мы захотим иметь серию изображений, а не только одно?
Техника та же, нам нужно повторить серию изображений, чтобы покрыть общую ширину. Представьте, что у нас есть массив «repeat». Нам нужно сделать два цикла. Наш массив с изображениями называется «логотипы»
<div #wrapper id="logo-gallery-wrapper">
<ul (mouseover)="player.pause()" (mouseout)="player.play()" #banner id="logo-gallery">
<li #logo>
<figure class="logo-gallery-figure">
<img (load)="resize()" [src]="logos[0].url">
</figure>
</li>
<ng-container *ngFor="let i of repeat;let firstRepeat=first">
<ng-container *ngFor="let logo of logos;let firstLogo=first">
<li *ngIf="!firstLogo || !firstRepeat">
<figure class="logo-gallery-figure">
<img [src]="logo.url">
</figure>
</li>
</ng-container>
</ng-container>
</ul>
</div>
Опять же, учтите, что сначала мы записываем первое изображение, а затем делаем два цикла. Это необходимо, потому что это первое изображение в событии «load», которое удаляет анимацию. И это невозможно добавить в цикл, потому что мы собираемся изменить «repeat array», и это приведет к тому, что мы создадим новую анимацию при каждом изменении размера.
Ну, это какой-то сложный цикл twoo, потому что нам не нужно рисовать это изображение
Опять же, у нас есть функция createAnimations
createAnimation() {
if (this.wrapper amp;amp; this.logo) {
//calculate the total width
const totalWidth = this.wrapper.nativeElement.getBoundingClientRect()
.width;
//number of copies necesary
let copies =
2 * Math.floor(totalWidth / (this.logos.length * this.logoWidth)) 1;
//at least must be 2
if (copies == 1) copies = 2;
//create an array with somany elements than "copies"
this.repeat = ".".repeat(copies).split("");
//we are going to move lo the left only the width of the "first group of images"
const inc = this.logoWidth * this.logos.length;
//rest of code similar, but the speed is proportional to inc
const time = 9.2 * inc "ms";
const myAnimation: AnimationFactory = this.builder.build([
style({ transform: `translateX(0px)` }),
animate(time, style({ transform: `translateX(${-inc}px)` }))
]);
this.player = myAnimation.create(this.banner.nativeElement);
this.player.onDone(() => {
this.createAnimation();
});
this.player.play();
}
}
(*) Чтобы проверить, все ли идет хорошо, мы можем изменить стиль .css на overflow:scroll
и комментирует эти строки, чтобы убедиться, что мы правильно вычисляем «копии» и «inc»
Как обычно, новый stackblitz с изменениями
Обновить и… что насчет логотипов разной ширины?
Ну, в этом случае мы собираемся изменить .html. Мы создаем цикл с логотипами, а другой — с повторением
<div #wrapper id="logo-gallery-wrapper">
<ul (mouseover)="player.pause()" (mouseout)="player.play()" #banner id="logo-gallery">
<li *ngFor="let logo of logos" #logo>
<figure class="logo-gallery-figure">
<img (load)="loaded()" [src]="logo.url">
</figure>
</li>
<ng-container *ngFor="let i of repeat">
<li *ngFor="let logo of logos">
<figure class="logo-gallery-figure">
<img [src]="logo.url">
</figure>
</li>
</ng-container>
</ul>
</div>
Посмотрите, что в этом случае (load)
событие вызывает загруженную функцию. Идея заключается в том, что в этой функции увеличьте переменную -picsLoaded — когда все логотипы загружены, мы вычисляем общую ширину. Видите, что нам нужно использовать ViewChildren, а не ViewChild
totalLogoWidth: number = 0;
picsLoaded=0;
@ViewChildren("logo") logo: QueryList<ElementRef>;
loaded()
{
this.picsLoaded ;
if (this.picsLoaded==this.logos.length)
{
let totalWidth=0;
this.logo.forEach(x=>{
totalWidth =x.nativeElement.getBoundingClientRect().width;
})
this.totalLogoWidth=totalWidth
this.createAnimation()
}
}
Теперь только что обновил функцию createAnimation, чтобы использовать totalLogoWidth вместо this.logos.длина*this.logoWidth
createAnimation() {
if (this.wrapper amp;amp; this.logo) {
const totalWidth = this.wrapper.nativeElement.getBoundingClientRect()
.width;
//in this case "copies" is simply Math.floor 1
let copies =Math.floor(totalWidth / (this.totalLogoWidth)) 1;
this.repeat = ".".repeat(copies).split("");
const inc = this.totalLogoWidth;
const time = 9.2 * inc "ms";
const myAnimation: AnimationFactory = this.builder.build([
style({ transform: `translateX(0px)` }),
animate(time, style({ transform: `translateX(${-inc}px)` }))
]);
this.player = myAnimation.create(this.banner.nativeElement);
this.player.onDone(() => {
this.createAnimation();
});
this.player.play();
}
}
Еще один стакблитц
Ответ №2:
вы можете поместить функцию js в компонент. ДЕМОНСТРАЦИЯ
import { Component, OnInit } from "@angular/core";
function createAnimation() {
let e = document.getElementById("logo-gallery");
var style = document.createElement("style");
style.type = "text/css";
let keyframes =
"@keyframes scroll-left { 0% {transform: translateX(0);}100% {transform: translateX(-"
e.scrollWidth
"px); }}";
style.innerHTML = keyframes;
document.getElementsByTagName("head")[0].appendChild(style);
e.setAttribute(
"style",
"animation: scroll-left 20s linear infinite; animation-iteration-count: infinite;"
);
}
@Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent implements OnInit {
name = "Angular";
ngOnInit() {
createAnimation();
}
}
Но я обычно не выбираю использовать функцию как способ, которым я выбираю поместить один css внутри component css Demo
@keyframes scroll-left
{
0% {transform: translateX(0)}
100% {transform: translateX(var(--m,100%))}
}
и дайте анимацию css
#logo-gallery {
animation: scroll-left 20s linear infinite;
animation-iteration-count: infinite;
}
и с Viwchild
@ViewChild('logoGallery', {static: false}) logoGallery: ElementRef;
ngAfterViewInit(): void {
let element=this.logoGallery.nativeElement;
element.style.setProperty('--m',element.scrollWidth "px");
}
Комментарии:
1. Но является ли это правильным (приятным) способом? Просто чтобы убедиться, что я все правильно выучил 🙂
2. Я отредактировал ответ с помощью второго способа, который я предпочитаю использовать вместо функции js
3. Работает хорошо, но как-то работает в неправильном направлении: D должно быть справа налево. Есть идеи?
4. В демо он работает справа налево. Вы поставили минус в качестве префикса в свойстве set? @Mr.Jo и Viewchild начинает работать первым в AfterViewInit
5. Это гораздо больше о css, и я не слишком хорошо разбираюсь в css. Вы можете задать это в stack overflow с тегом css в качестве нового вопроса. Тогда вы можете получить ответ @Mr.Jo