前端網頁動效
-如何用原生 JS 和 CSS 做 3D carousel?
前陣子看到台灣服飾品牌 namesake 的網站,它們產品介紹的頁面是使用 3d carousel,覺得超酷 👇
研究一下之後其實不會很難,今天就帶你用原生 JS 和 CSS 做出一樣的特效!👇
原理介紹
整個效果的原理不難,先用 CSS 3d 並控制每個 image 旋轉的角度以及偏移,再利用 JS 控制整個 container 的位移和旋轉效果即可,接下來就讓我們從 HTML 開始看吧!
HTML
HTML 架構不複雜,基本上就是兩層 container,外層負責 x、z 的偏移,內層負責旋轉。
並且在每張圖片宣告 —index 的 css 變數等等用來控制旋轉的角度。
<div class="images">
<div class="images__rotator">
<div
class="images__item"
style="--index: 0"
>
<img src="./assets/1.jpg">
</div>
<div
class="images__item"
style="--index: 1"
>
<img src="./assets/2.jpg">
</div>
<div
class="images__item"
style="--index: 2"
>
<img src="./assets/3.jpg">
</div>
<!-- 總共 12 張 -->
<div
class="images__item"
style="--index: 11"
>
<img src="./assets/12.jpg">
</div>
</div>
</div>
CSS
先將 body 設定 overflow: hidden
以及等等 transition 要用到的 timeline
:root {
--ease-out-circ: cubic-bezier(0, 0.55, 0.45, 1);
}
body {
overflow: hidden;
}
接著設定 images
的 style,包括圖片張數、每張圖片的寬度、位移,以及滑鼠滾動的大小。因為要用到 3d 的效果,所以記得加上 transform-style: preserve-3d;
。
rotator 也設定寬高,讓他撐滿整個螢幕,並也加上 transform-style: preserve-3d;
.images {
--image-count: 12;
--image-width: calc(300vw / var(--image-count));
--shift: 4vw * var(--image-count);
--wheel-momentum: 0;
position: relative;
transform-style: preserve-3d; /* 👈 */
transition: 2s var(--ease-out-circ);
}
.images__rotator {
width: 100vw;
height: 100vh;
transform-style: preserve-3d;
transition: 2s var(--ease-out-circ);
}
接著設定每張 image__item
的 style。用 aspect-ration 設定比例並讓 img 稱滿 image__item
。
.images__item {
--offset-z: calc(var(--shift) + var(--wheel-momentum) * 0.2px);
position: absolute;
width: var(--image-width);
aspect-ratio: 9 / 16;
top: 50%;
left: 50%;
transform:
translate3d(-50%, -50%, 0)
rotateY(calc((360deg / var(--image-count)) * var(--index)))
translateZ(var(--offset-z))
scale(calc(1 - var(--wheel-momentum)));
transition: transform 0.8s;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
transform 看起來稍微複雜一點,讓我一個一個解釋:
translate3d(-50%, -50%, 0)
: 將圖片置中rotateY(calc((360deg / var(--image-count)) * var(--index)))
: 讓每張圖依序旋轉一樣的角度--offset-z: calc(var(--shift) + var(--wheel-momentum) * 0.2px)
和translateZ(var(--offset-z))
: 當滑鼠滾動時會增加 z 軸的偏移量,讓圖片往外擴展的感覺scale(calc(1 - var(--wheel-momentum) / 1000))
: 滾動時讓圖片縮小
此時讓我們稍微旋轉一下 .images
的角度就可以看到每張圖片已經排成 3D carousel 的樣子了。
JavaScript
接著來做 JS 吧!先獲得相關的 DOM,接著我們要監聽 wheel 和 mousemove 的事件,並控制 旋轉的角度以及調整 wheelMomentum (滾動力度)。加載完 DOM 後執行 runAnimation 並搭配 requestAnimationFrame 來做動畫。
const images = document.querySelector('.images');
const imagesRotator = document.querySelector('.images__rotator');
const rotationAngles = { x: -4, y: 200, z: 0 };
document.body.addEventListener('wheel', e => {
const friction = 12;
const wheel = e.deltaY / friction;
rotationAngles.y -= wheel;
setWheelMomentum(wheel);
})
document.body.addEventListener('mousemove', e => {
const y = e.clientY - (innerHeight / 2);
const x = e.clientX - (innerWidth / 2);
rotationAngles.x = y / 60;
rotationAngles.z = x / 100;
});
function runAnimations() {
animateImages()
animateImagesRotator()
requestAnimationFrame(runAnimations);
}
document.addEventListener('DOMContentLoaded', runAnimations);
有了基本 JS 架構後,我們來看更細節的函數。
animateImages 和 animateImagesRotator 做的事情很簡單,就只是利用 setProperty 控制 DOM 的 style 而已。
const animateImages = () => {
images.style.setProperty('transform',
`rotateX(${rotationAngles.x}deg) rotateZ(${rotationAngles.z}deg)`);
}
const animateImagesRotator = () => {
imagesRotator.style.setProperty('transform', `rotateY(${rotationAngles.y}deg)`);
}
setWheelMomentum
也不難,基本上就是賦予 css 變數—whell-momentum
的值,並在 1.8 秒後將 --wheel-momentum
設為 0。
let wheelMomentumTimeout = null;
const setWheelMomentum = (momentum = 1) => {
const friction = 1000;
images.style.setProperty('--wheel-momentum', Math.abs(momentum / friction));
clearTimeout(wheelMomentumTimeout);
wheelMomentumTimeout = setTimeout(() => {
images.style.setProperty('--wheel-momentum', 0);
}, 1800);
}
到這邊就大功告成了!其實沒有想像中難吧!如果想要更好的動畫效果,可以嘗試用 gsap 動畫函式庫來調整,會讓整個動畫更滑順。
那今天就這樣!下篇貼文見囉~!