Angular's Innovative 3D-esque Carousel Rotation

My goal is to design a pseudo-3d carousel with 5 items, like the example below (and have them rotate around):

https://i.sstatic.net/FtcSe.png

I came across this fantastic stackblitz as a base, and I've been tinkering with it for hours attempting to modify it to have 5 items instead of 3, but I'm struggling to fully grasp how it functions.

If there's someone more knowledgeable who can guide me on how to achieve this with 5 items, it would be greatly appreciated. I'm especially confused about how to configure the movements variable, which I believe is key to making this work.

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

Answer №1

Apologies for the confusion in my previous explanation. Let's try to clarify the concept with a different approach. Imagine a carousel displaying 5 items at a time. To visualize this setup, picture a circle divided into 8 parts, with each segment numbered starting from the bottom as 0 and progressing clockwise. This layout corresponds to the faces visible in the carousel, which are numbered 0, 1, 2, 6, and 7.

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

The movements of the faces can be represented as follows:

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

When calculating the angle for movement, remember to divide by 8, not 9, for accurate positioning.

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

For a more realistic movement, consider dividing by 16 instead of 8. In this case, the visible faces would be 0, 2, 4, 12, and 14, located as follows:

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

And their corresponding movements 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, 3, 2] },
    { pos: 14, right: [15, 0], left: [13, 12] },
  ];

Adjust the angle calculation by dividing by 16:

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

Implement two-step movements within the carousel for enhanced functionality. Define the movements accordingly and update the animation function to support single or double-step movements.

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

Update the animation function to accommodate one or two-step movements:

  animateViews(direction: string, steps:number=1) {
    this.animates.forEach((x: number, index: number) => {
      //use the appropriate array based on the number of steps
      const movements=steps==1?this.movements:this.movementsTwo;
      const mov = movements.find(m => m.pos == x);
      ...
  }

Introduce the functionality to move a specific element to the front within the carousel:

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

Include a dropdown menu for easy selection:

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

Alternatively, add a click event to the carousel elements for intuitive navigation:

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

For a more interactive experience, consider a pseudo-3D carousel designed to display 5 images simultaneously. Utilize CSS transform for smooth card movements and perspective effects.

Feel free to explore the updated functionality in a new stackblitz.

NOTE: While the current implementation may seem hardcoded, future enhancements may involve creating a more versatile component for dynamic usage.

Answer №2

NOTE: This is a continuation of my previous response.

I have successfully created a component that allows for an odd number of figures.

The key innovation is simplifying the values of animations. Rather than skipping numbers, I have numbered them from 0 to 2 times the length minus 2. For example:

  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]

No need to worry, we generate the array when passing the images in the @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))
    
  }

First, we calculate the array "pos" using a function that can hold both numbers and fractions. For example, [3.5, 3] or [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;
  }

An auxiliary function provides the "animations"

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

Our function "animateViews" is now structured as follows

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

Finally, to bring an image to the front, we calculate the necessary steps to reach the "0" position

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

Check out the final stackblitz

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Encountering a problem when attempting to utilize AngularFire's Cloud Messaging Angular module due to missing configuration values for the app

Working with Firebase Cloud Messaging in my Angular application using AngularFire2 has presented a challenge. I am facing an error when attempting to retrieve the user's current token. I have already set up the "firebase-messaging-sw.js" and "manifes ...

In JavaScript coding language, you can use this syntax to create an array and push the

When the variable foo is defined, why does the following code behave in a certain way? var array = [].push(foo); When executed, why does the output equal 1? Based on my testing, the variable array simply returns the length of the array. Therefore, if ...

What is the best way to apply a background color to text seamlessly? (Using HTML5 and CSS3)

<!-- 6장 연습문제 4 --> <html lang = "ko"> <head> <meta charset = "UTF-8"> <style> img{margin-left: auto; margin-right: auto; display: block;} .hyper{ text-decoration-line: ...

Steps for triggering Docusign Clickwrap with a button press

My current project involves integrating docusign clickwrap codes. I want the clickwrap to appear when a user clicks a button. However, when I use the code below, the clickwrap opens directly. How can I trigger the ds-click using a button click event? For ...

Unable to display tags with /xoxco/jQuery-Tags-Input

I'm experimenting with the tagsinput plugin in a textarea located within a div that is loaded using the jquery dialog plugin. The specific plugin I am using is /xoxco/jQuery-Tags-Input. After checking that the textarea element is ready, I noticed th ...

A guide to resetting items directly from a dropdown select menu

I need help with clearing or resetting select options directly from the dropdown itself, without relying on an external button or the allowClear feature. Imagine if clicking a trash icon in the select option could reset all values: https://i.stack.imgur. ...

utilizing Javascript to insert a class with a pseudo-element

Witness the mesmerizing shining effect crafted solely with CSS: html, body{ width: 100%; height: 100%; margin: 0px; display: table; background: #2f2f2f; } .body-inner{ display: table-cell; width: 100%; height: 100%; ...

How can I duplicate or extract all the formatting applied to a specific text selection in Ckeditor?

I am currently utilizing CKEditor version 3.6 within my Asp.net MVC 3 Application. One of my tasks involves creating a Paint format option similar to Google Docs. I am looking to integrate this feature into CKEditor. Is there a way in CKEditor to transfe ...

What is the method to retrieve the value from $.get()?

After searching on stackoverflow, I was unable to locate a solution to my issue and am currently unable to comment for further assistance. The dilemma I face involves extracting a return value from an asynchronous method to pass it onto another function: ...

Select an image based on the input value provided

I'm new to coding and I'm attempting to replicate the search functionality of icomoon where typing a word displays related images. However, I'm facing an issue where I can't seem to get the value entered in the input field to trigger an ...

The ngtools/webpack error is indicating that the app.module.ngfactory is missing

I'm currently attempting to utilize the @ngtools/webpack plugin in webpack 2 to create an Ahead-of-Time (AoT) version of my Angular 4 application. However, I am struggling to grasp the output generated by this plugin. Specifically, I have set up a ma ...

Extracting information from JSON using jQuery

this is a sample json object: { "box 1": [{ "difficulty_grade": "5a+" }, { "gym": "New_gym" }, { "route_author": "some author" },]} https://i.sstatic.net/UJodJ.png Here is the code snippet: variable groups contains JSON data as shown in the ...

Ajax - Every single request has been successfully fulfilled

I am struggling to determine when all my Ajax requests are finished in order to call another function. I have attempted to use various methods with native Ajax functions in Chrome, but none of them seem to be working for me. Can someone please help me? H ...

DNN Unveils New "Exit Confirmation" Pop-up Feature When Clicking External Links

Greetings fellow beginners! I've been struggling to make some changes on our DNN site (Evoq 8.5) with no success so far. The issue at hand is that we have links throughout our entire website that follow this format: <a href="www.site.com" class="e ...

Create additional object property and include in current object's properties dynamically

I have a JSON object that looks like this: "highChart":{ "xAxis":{ "categories":[ "SYN 13 ", "Settlement", "Service Interaction", "FNOL", ...

Trapped in the dilemma of encountering the error message "Anticipated an assignment or function: no-unused expressions"

Currently facing a perplexing issue and seeking assistance from the community to resolve it. The problem arises from the code snippet within my model: this.text = json.text ? json.text : '' This triggers a warning in my inspector stating: Ex ...

Tips for launching different web browsers via hyperlinks?

My app has a link that I want mobile users from apps like LinkedIn to open in a browser such as Safari. I attempted this: <a href="safari-https://meed.audiencevideo.com">May open on Safari</a>' However, when I click the link, it opens i ...

Can you explain the functions of this "malicious" JavaScript code?

I came across this piece of code on a website that labeled it as "malicious" javascript. Given my limited knowledge of javascript and the potential risks involved in trying out the code on my own site, I was hoping someone here might be able to shed some l ...

Mastering the correct way to handle the "window" object within the Node.js testing environment using JSDom

Testing my React app using Tape and JSDom involves importing a specific module at the beginning of each test JS file: import jsdom from 'jsdom' function setupDom() { if (typeof document === 'undefined') { global.document = jsdom ...

JavaScriptCore now includes the loading of AMD modules

I am trying to incorporate a JavaScript module into JavascriptCore on iOS. To achieve this, I am fetching the file's text through a standard HTTP request on the iOS platform. Once I have obtained the entire string, I plan to parse it into the JSconte ...