Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rotating psuedo-3d carousel in Angular

I'm trying to create a psuedo-3d carousel with 5 items like the following (and have them cycle around):

enter image description here

I found this great stackblitz as a starting point, and have been playing with it for hours trying to make it have 5 items instead of 3 but I'm having a really hard time understanding how it works.

If someone smarter than me can help me understand how to make this 5 items it would be great. In particular, I dont understand how this movements variable should be configured, and that is probably the entire secret sauce here.

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] }
];
like image 325
parliament Avatar asked Feb 14 '26 20:02

parliament


1 Answers

sorry, I think I the author. and I explained very bad the code. See that you see in your carousel 5 items at time, So draw a circunference and divide in 8 part. numbered so the most bottom as 0, and to the left 1, 2,... some like

    4
 3     5
2       6
 1     7
    0

The animates (the faces you see) are 0,1,2,6 and 7

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

And the position are

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] },
  ];

that, e.g. the face is in position 0, to the rigth goes to position 1, to the left goes to position 7 the face is in position 1, to the rigth goes to position 1, to the left goes to position 0 the face is in position 2, to the rigth goes to positions 3,4,5 and 6, to the left goes to position 1.

Well, you need take carefull, when we calculate the angle, you divided by 8 (not by 9)

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

You can choose, instead of divided by 8, divided by 16, to make the movement more realistic. In this case you see the faces

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

See that in this case the faces visible are 0,2,4,12 y 14

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

The positions are

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] },
  ];

And you need divided by 16

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

I forked the stackblitz here

Update the carousel only move to left, rigth one step. We can improve it if we defined other movements to allow move to left and right two steps

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] },

  ]

And change the function animateViews to allow move one or two steps

  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);
      ...
  }

This allow us write a function to "move to front" one element

  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);
    }
  }

Well, the only is write a select

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

Or add a (click) event to our "carousel"

<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>

I create another stackblitz

NOTE: Really "harcoded" the movemnet don't like so much. If I have a bit time I'll try to create a component, but I can not promise when :(

Update 2 I made a component speudo-carousel for 5 images. The main change is that, in this one, I use transfom to move the "cards". So the function animateView becomes like

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)
        );
      });
      ....
    });
  }

This makes easer the .css that is only

.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);
}

So the component are not so dependent for the sizes of the images. See also that I add a trasnformY to give a sensation to "view from above"

The changes in a new stackblitz

NOTE: By the moment, if we want to change the .css we can use the styles.css and write, e.g.

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

}
like image 115
Eliseo Avatar answered Feb 16 '26 09:02

Eliseo