【问题标题】:Rotating psuedo-3d carousel in Angular在Angular中旋转psuedo-3d轮播
【发布时间】:2022-01-26 10:52:24
【问题描述】:

我正在尝试创建一个包含 5 个项目的伪 3d 轮播,如下所示(并让它们循环播放):

我发现这个很棒的 stackblitz 是一个起点,并且已经玩了几个小时试图让它有 5 个项目而不是 3 个,但我很难理解它是如何工作的。

如果比我聪明的人可以帮助我了解如何制作这 5 件物品,那就太好了。特别是,我不明白这个movements变量应该如何配置,这可能就是这里的全部秘密。

movements = [
    { pos: 0, right: [1, 2], left: [8, 7] },
    { pos: 2, right: [3, 4, 5, 6, 7], left: [1, 0] },
    { pos: 7, right: [8, 0], left: [6, 5, 4, 3, 2] }
];

【问题讨论】:

    标签: javascript css angular


    【解决方案1】:

    对不起,我想我是作者。我解释得很糟糕的代码。看到你在轮播中看到了 5 个项目,所以画一个圆圈并分成 8 个部分。编号所以最底部为 0,左侧为 1、2、... 一些类似

        4
     3     5
    2       6
     1     7
        0
    

    动画(您看到的面孔)是 0、1、2、6 和 7

    animates = [0, 1,2,6,7];
    

    位置是

    movements = [
        { pos: 0, right: [1], left: [7] },
        { pos: 1, right: [2], left: [0] },
        { pos: 2, right: [3,4,5,6], left: [1] },
        { pos: 6, right: [7], left: [5,4,3,2] },
        { pos: 7, right: [0], left: [6] },
      ];
    

    那个,例如 脸部在位置 0,向右转到位置 1,向左转到位置 7 脸部在位置 1,向右转到位置 1,向左转到位置 0 人脸位于位置 2,右侧为位置 3、4、5 和 6,左侧为位置 1。

    好吧,你需要小心,当我们计算角度时,你除以8(不是9)

    const animations = mov[direction].map(m => {
        const angle = (m * 2 * Math.PI) / 8; //<--see the "8"   
        ....
    }
    

    您可以选择,而不是除以 8,除以 16,以使运动更逼真。在这种情况下,您会看到面孔

              8
           7     9
        6          10
      5              11 
     4                12
      3              13
        2          14
          1      15
              0
    

    看到在这种情况下可见的面是 0,2,4,12 y 14

    animates=[0,2,4,12,14]
    

    职位是

    movements = [
        { pos: 0, right: [1,2], left: [15,14] },
        { pos: 2, right: [3,4], left: [1,0] },
        { pos: 4, right: [5,6,7,8,9,10,11,12], left: [3,2] },
        { pos: 12, right: [13,14], left: [11,10,9,8,7,6,5,4] },
        { pos: 14, right: [15,0], left: [13,12] },
      ];
    

    你需要除以 16

    const animations = mov[direction].map(m => {
        const angle = (m * 2 * Math.PI) / 16; //<--see the "16"   
        ....
    }
    

    我分叉了 stackblitz here

    更新轮播仅向左移动,仅一步。如果我们定义其他动作允许左右两步移动,我们可以改进它

    movementsTwo=[
        { pos: 0, right: [1,2,3,4], left: [15,14,13,12] },
        { pos: 2, right: [3,4,5,6,7,8,9,10,11,12], left: [1,0,15,14] },
        { pos: 4, right: [5,6,7,8,9,10,11,12,13,14], left: [3,2,1,0] },
        { pos: 12, right: [13,14,15,0], left: [11,10,9,8,7,6,5,4,3,2] },
        { pos: 14, right: [15,0,1,2], left: [13,12,11,10,9,8,7,6,5,4] },
    
      ]
    

    并更改函数 animateViews 以允许移动一两步

      animateViews(direction: string,steps:number=1) {
        this.animates.forEach((x: number, index: number) => {
          //here use one or another array
          const movements=steps==1?this.movements:this.movementsTwo;
          const mov = movements.find(m => m.pos == x);
          ...
      }
    

    这允许我们编写一个函数来“移动到前面”一个元素

      indexToFront(index: any) {
        index = +index;
        const pos = this.animates[+index];
        if (pos) {
          const mov = this.movements.find((x) => x.pos == pos);
          const mov2 = this.movementsTwo.find((x) => x.pos == pos);
          const anim = { direction: 'right', steps: 1 };
          if (
            mov.left[mov.left.length - 1] == 0 ||
            mov2.left[mov2.left.length - 1] == 0
          )
            anim.direction = 'left';
          if (
            mov2.left[mov2.left.length - 1] == 0 ||
            mov2.right[mov2.right.length - 1] == 0
          )
            anim.steps = 2;
    
          this.animateViews(anim.direction, anim.steps);
        }
      }
    

    好吧,唯一的就是写一个选择

    <select #select (change)="indexToFront(select.value)">
      <option *ngFor="let i of [0,1,2,3,4]">{{i}}</option>
    </select>
    

    或将(click) 事件添加到我们的“轮播”

    <div style="position:relative;margin-top:150px">
      <div class="carousel" >
        <div #element *ngFor="let i of [0,2,4,12,14];let index=index" 
           (click)="animates[index]!=0 && indexToFront(index)" 
           class="carousel__cell">{{index}}
          </div>
    </div>
    

    我创建another stackblitz

    注意:真正“编码”的 movemnet 不太喜欢。如果我有一点时间,我会尝试创建一个组件,但我不能保证什么时候:(

    更新 2 我为 5 张图片制作了一个组件伪轮播。主要的变化是,在这一个中,我使用变换来移动“卡片”。所以函数 animateView 就变成了

    animateViews(direction: string, steps: number = 1) {
        this.animates.forEach((x: number, index: number) => {
          const movements = steps == 1 ? this.movements : this.movementsTwo;
          const mov = movements.find((m) => m.pos == x);
          const item = this.itemsView.find((_x, i) => i == index);
          const animations = mov[direction].map((m) => {
            const angle = (m * 2 * Math.PI) / 16;
            const scale =
              (1 + this.minScale) / 2 + ((1 - this.minScale) / 2) * Math.cos(angle);
            const applystyle = {
              transform:'translate('+
                   (this.radius * Math.sin(angle)) + 'px,'+
                   (Math.floor(this.top * scale)-this.top)+'px) 
                   scale('+scale+')',
              'z-index': Math.floor(100 * scale),
            };
            return animate(
              this.timer / mov[direction].length + 'ms',
              style(applystyle)
            );
          });
          ....
        });
      }
    

    这使得 .css 更简单

    .carousel{
      display:flex;
      margin-left:auto;
      margin-right:auto;
      margin-top:20px;
    
    }
    .carousel::after{
      content:' ';
      display: block;
      clear: both;
    }
    
    .carousel>div{
      float:left;
      margin-right:-100%;
      cursor:pointer;
      transform-origin: top left;
    }
    
    .carousel>div img {
      border-radius: 1rem;
      box-shadow: 0 .5rem 1rem rgba(0,0,0,.35);
    }
    

    因此组件对图像大小的依赖性不大。另请参阅我添加了一个 trasnformY 以给“从上方查看”的感觉

    new stackblitz中的变化

    注意:目前,如果我们想更改 .css,我们可以使用 styles.css 并编写,例如

    .custom .carousel>div img {
      border-radius: 50%!important;
      border:2px solid white;
    
    }
    

    【讨论】:

    • 太棒了,谢谢!不太确定你是如何找到这个问题的,但显然没有人能更好地回答它:)
    • @parliament,最初的 stackblitz 是对此的回应(有点旧)SO
    • 您是否知道通过索引为特定幻灯片制作动画的方法?我试图在轮播下创建 5 个指示符“点”,用户可以单击它们。由于 animateView 一次只能运行一个步骤,我假设我必须在调用播放器之前收集并合并多个动画集,但这也被证明是相当棘手的。
    • @parliament,我更新了答案。我没有时间制作一个组件,只是简单地将轮播硬编码为 5 张“卡片”,也许我会有一些时间制作一个组件,但我不能保证:(
    【解决方案2】:

    注意:这是我另一个回复的延续。

    最后我制作了一个允许奇数个数字的组件。

    第一个是简化动画的值,而不是编号跳过一个,我从 0 编号到 2*length-2。所以,例如

      Five images            three images
          4
       3     5                   2  
      2       6               1     3
       1     7                   0
          0
    animates=[0,1,2,6,7]   animates=[0,1,3]
    

    别着急,我们在传递@Input中的图片时会创建数组

    @Input('images') set _(value:string[])
      {
        this.images=value;
        this.length=value.length;
        this.array=new Array(this.length).fill(0).map((_,x)=>x)
        this.animates=new Array(this.length*2-2).fill(0)
                  .map((_,x)=>x)
                  .filter(x=>(x <= this.length / 2 || x > (3 * this.length) / 2 - 2))
        
      }
    

    首先我们使用函数计算数组“pos”。看到在这种情况下,数组 pos 可以保存数字和小数。例如在一种情况下可以像 [3.5, 3][5.5, 5, 4.5, 4, 3.5, 3, 2.5, 2]

    getMovement(posIni: number, incr: number, steps: number, length: number) {
        if (steps == 0) return [posIni];
        const pos = [];
        let index = posIni;
        let cont = 0;
        while (cont < steps) {
          index += incr / 2;
          index = (index + 2 * length - 2) % (2 * length - 2);
          if ((index * 2) % 2 == 0) {
            pos.push(index);
            if (index <= length / 2 || index > (3 * length) / 2 - 2) cont++;
          } else pos.push(index);
        }
        return pos;
      }
    

    辅助功能为我们提供“动画”

    getAnimation(pos: number, length: number, timer: number) {
        const angle = (pos * 2 * Math.PI) / (2 * length - 2);
        const scale =
          (1 + this.minScale) / 2 + ((1 - this.minScale) / 2) * Math.cos(angle);
        const applystyle = {
          transform:
            'translate(' +
            this.radius * Math.sin(angle) +
            'px,' +
            (Math.floor(this.top * scale) - this.top) +
            'px) scale(' +
            scale +
            ')',
          'z-index': Math.floor(100 * scale),
        };
        return animate(timer + 'ms', style(applystyle));
      }
    

    我们的函数“animateViews”变成了

      animateViews(direction: number, steps: number = 1) {
        this.animates.forEach((x: number, index: number) => {
          const pos = this.getMovement(x, direction, steps, this.length);
          const time = this.timer / pos.length;
          const animations = pos.map((x) => this.getAnimation(x, this.length, time));
          const item = this.itemsView.find((_x, i) => i == index);
    
          const myAnimation = this.builder.build(animations);
          this.player = myAnimation.create(item.nativeElement);
          this.player.onDone(() => {
            this.animates[index] = pos[pos.length - 1];
          });
          this.player.play();
        });
      }
    

    最后,当我们将图像放在前面时,我们简单地计算获得“0”位置的必要步骤

      indexToFront(index: any) {
        const pos=this.animates[index]
        if (pos != 0) {
          const steps = pos<=this.length/2?-pos:2*this.length-2-pos
          const factor=steps<0?-1:1;
          this.animateViews(factor,factor*steps)
        }
        this.select.emit(index);
      }
    

    final stackblitz

    【讨论】:

    • 这太棒了,谢谢。我已经开始赏金以奖励您提供的广泛帮助。一旦它允许我将在 24 小时内奖励。再次感谢
    • @parliament,请不要。对我来说,“发展”很有趣。此外,如果我能帮助别人,我很高兴。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-01-19
    • 1970-01-01
    • 1970-01-01
    • 2015-03-31
    • 2020-08-08
    • 1970-01-01
    • 2015-12-07
    相关资源
    最近更新 更多