I wanted to make a 3d rotating carousel like in the stack snippet, but required is a view from inside like in the image below. How can i achieve this?
window.addEventListener('load', () => { var carousels = document.querySelectorAll('.carousel'); for (var i = 0; i < carousels.length; i++) { carousel(carousels[i]); } }); function carousel(root) { var figure = root.querySelector('figure'), nav = root.querySelector('nav'), images = figure.children, n = images.length, gap = root.dataset.gap || 0, bfc = 'bfc' in root.dataset, theta = 2 * Math.PI / n, currImage = 0; setupCarousel(n, parseFloat(getComputedStyle(images[0]).width)); window.addEventListener('resize', () => { setupCarousel(n, parseFloat(getComputedStyle(images[0]).width)) }); setupNavigation(); function setupCarousel(n, s) { var apothem = s / (2 * Math.tan(Math.PI / n)); figure.style.transformOrigin = `50% 50% ${- apothem}px`; for (var i = 0; i < n; i++) { images[i].style.padding = `${gap}px`; } for (i = 1; i < n; i++) { images[i].style.transformOrigin = `50% 50% ${- apothem}px`; images[i].style.transform = `rotateY(${i * theta}rad)`; } if (bfc) { for (i = 0; i < n; i++) { images[i].style.backfaceVisibility = 'hidden'; } } rotateCarousel(currImage); } function setupNavigation() { nav.addEventListener('click', onClick, true); function onClick(e) { e.stopPropagation(); var t = e.target; if (t.tagName.toUpperCase() != 'BUTTON') { return; } if (t.classList.contains('next')) { currImage++; } else { currImage--; } rotateCarousel(currImage); } } function rotateCarousel(imageIndex) { figure.style.transform = `rotateY(${imageIndex * -theta}rad)`; } }
body { margin: 0; font-family: 'Roboto', sans-serif; font-size: 16px; } h1 { text-align: center; margin-bottom: 1.5em; } h2 { text-align: center; color: #555; margin-bottom: 0; } .carousel { padding: 20px; perspective: 500px; overflow: hidden; display: flex; flex-direction: column; align-items: center; } .carousel>* { flex: 0 0 auto; } .carousel figure { margin: 0; width: 40%; transform-style: preserve-3d; transition: transform 0.5s; } .carousel figure img { width: 100%; box-sizing: border-box; padding: 0 0px; } .carousel figure img:not(:first-of-type) { position: absolute; left: 0; top: 0; } .carousel nav { display: flex; justify-content: center; margin: 20px 0 0; } .carousel nav button { flex: 0 0 auto; margin: 0 5px; cursor: pointer; color: #333; background: none; border: 1px solid; letter-spacing: 1px; padding: 5px 10px; }
<h2>Eight images, with 80px gap</h2> <div class="carousel" data-gap="80"> <figure> <img src="https://source.unsplash.com/VkwRmha1_tI/800x533" alt=""> <img src="https://source.unsplash.com/EbuaKnSm8Zw/800x533" alt=""> <img src="https://source.unsplash.com/kG38b7CFzTY/800x533" alt=""> <img src="https://source.unsplash.com/nvzvOPQW0gc/800x533" alt=""> <img src="https://source.unsplash.com/mCg0ZgD7BgU/800x533" alt=""> <img src="https://source.unsplash.com/1FWICvPQdkY/800x533" alt=""> <img src="https://source.unsplash.com/bjhrzvzZeq4/800x533" alt=""> <img src="https://source.unsplash.com/7mUXaBBrhoA/800x533" alt=""> </figure> <nav> <button class="nav prev">Prev</button> <button class="nav next">Next</button> </nav> </div>
Advertisement
Answer
The carousel needs to be rotated and translated in the opposite direction to make it an inner rotation view.
The transform-origin
sets the center of the carousel. The first two numbers are x and y, which just center the carousel on the screen. The third number is z which determines whether the carousel is moved towards the screen or away from the screen. The negative sign on apothem
is removed to pull it out of the screen so the center becomes closer to the camera and achieves the inner carousel. backface-visibility
needs to be always set to hidden because there might be images behind the camera now. Then to fix the rotation direction with the next button, * -theta
is changed to positive.
window.addEventListener('load', () => { var carousels = document.querySelectorAll('.carousel'); for (var i = 0; i < carousels.length; i++) { carousel(carousels[i]); } }); function carousel(root) { var figure = root.querySelector('figure'), nav = root.querySelector('nav'), images = figure.children, n = images.length, gap = root.dataset.gap || 0, theta = 2 * Math.PI / n, currImage = 0; setupCarousel(n, parseFloat(getComputedStyle(images[0]).width)); window.addEventListener('resize', () => { setupCarousel(n, parseFloat(getComputedStyle(images[0]).width)) }); setupNavigation(); function setupCarousel(n, s) { var apothem = s / (2 * Math.tan(Math.PI / n)); figure.style.transformOrigin = `50% 50% ${apothem}px`; for (var i = 0; i < n; i++) { images[i].style.padding = `${gap}px`; } for (i = 1; i < n; i++) { images[i].style.transformOrigin = `50% 50% ${apothem}px`; images[i].style.transform = `rotateY(${i * theta}rad)`; } rotateCarousel(currImage); } function setupNavigation() { nav.addEventListener('click', onClick, true); function onClick(e) { e.stopPropagation(); var t = e.target; if (t.tagName.toUpperCase() != 'BUTTON') { return; } if (t.classList.contains('next')) { currImage++; } else { currImage--; } rotateCarousel(currImage); } } function rotateCarousel(imageIndex) { figure.style.transform = `rotateY(${imageIndex * theta}rad)`; } }
body { margin: 0; font-family: 'Roboto', sans-serif; font-size: 16px; } h1 { text-align: center; margin-bottom: 1.5em; } h2 { text-align: center; color: #555; margin-bottom: 0; } .carousel { padding: 20px; perspective: 500px; overflow: hidden; display: flex; flex-direction: column; align-items: center; } .carousel>* { flex: 0 0 auto; } .carousel figure { margin: 0; width: 40%; transform-style: preserve-3d; transition: transform 0.5s; } .carousel figure img { width: 100%; box-sizing: border-box; padding: 0 0px; backface-visibility: hidden; } .carousel figure img:not(:first-of-type) { position: absolute; left: 0; top: 0; } .carousel nav { display: flex; justify-content: center; margin: 20px 0 0; } .carousel nav button { flex: 0 0 auto; margin: 0 5px; cursor: pointer; color: #333; background: none; border: 1px solid; letter-spacing: 1px; padding: 5px 10px; }
<h2>Eight images, with 80px gap</h2> <div class="carousel" data-gap="80"> <figure> <img src="https://source.unsplash.com/VkwRmha1_tI/800x533" alt=""> <img src="https://source.unsplash.com/EbuaKnSm8Zw/800x533" alt=""> <img src="https://source.unsplash.com/kG38b7CFzTY/800x533" alt=""> <img src="https://source.unsplash.com/nvzvOPQW0gc/800x533" alt=""> <img src="https://source.unsplash.com/mCg0ZgD7BgU/800x533" alt=""> <img src="https://source.unsplash.com/1FWICvPQdkY/800x533" alt=""> <img src="https://source.unsplash.com/bjhrzvzZeq4/800x533" alt=""> <img src="https://source.unsplash.com/7mUXaBBrhoA/800x533" alt=""> </figure> <nav> <button class="nav prev">Prev</button> <button class="nav next">Next</button> </nav> </div>