Как я могу использовать мою функцию JavaScript слайдера логотипа внутри углового компонента / TypeScript?

#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