前言
前段时间把《灵笼》给看完了,个人觉得非常不错(除了更新比较慢)。里面的角色给我留下了很深的印象。比如冉冰,荷光者樊蒂,白月魁等。对了,还有马克(不然你们说我只记得妹子)。由于喜欢的角色众多,所以就想着写个东西来展示。于是3D旋转相册由此而生。好了,话不多说,下面开始一步步实现。
页面结构
<style type='text/css'>
* {
margin: 0;
padding: 0;
}
.container {
display: flex;
min-height: 100vh;
perspective: 800px;
background: #000;
touch-action: none;
}
.wrap {
position: relative;
width: 120px;
height: 180px;
margin: auto;
transform-style: preserve-3d;
pointer-events: none;
}
.wrap img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 2px;
}
</style>
<div class="container">
<div class="wrap">
<img src="../images/incarnation/baiyuekui.jpg" alt="">
<img src="../images/incarnation/heguanzhe.jpg" alt="">
<img src="../images/incarnation/ailika.jpg" alt="">
<img src="../images/incarnation/jiexika.jpg" alt="">
<img src="../images/incarnation/chenmin4277.jpg" alt="">
<img src="../images/incarnation/feixue.jpg" alt="">
<img src="../images/incarnation/hongkou.jpg" alt="">
<img src="../images/incarnation/peini.jpg" alt="">
<img src="../images/incarnation/suixing.jpg" alt="">
<img src="../images/incarnation/jingnan.jpg" alt="">
<img src="../images/incarnation/xiadou.jpg" alt="">
<img src="../images/incarnation/ranbing.jpg" alt="">
</div>
</div>
上面使用到了perspective和transform-style,实现3D效果。利用定位将所有图片重叠。
开场动画
// 获取dom
const container = document.querySelector('.container');
const wrap = document.querySelector('.wrap');
const imgList = document.querySelectorAll('.wrap img');
const length = imgList.length;
// 计算图片间隔角度
const angle = 360 / length;
// 开场动画 延时1000 / 60 = 16.666667 ≈ 17,否则transition不会生效
setTimeout(() => {
for (let i = 0; i < length; i++) {
// 每张图片过渡效果间隔0.1s
imgList[i].style.transition = 'transform 1s ease ' + (length - 1 - i) * 0.1 + 's';
// 沿着z轴偏移320像素(此距离自己设置,觉得合适即可),否则图片会挤在一起
imgList[i].style.transform = 'rotateY(' + (angle * i) + 'deg) translateZ(320px)';
}
}, 17);
// wrap沿x轴旋转-10度
const rotate = { x: -10, y: 0 };
wrap.style.transform = 'rotateX(' + rotate.x + 'deg)';
拖拽查看
let isPointerDown = false;
let point = null;
let last = null;
let diff = null;
let rafId = null;
// 监听pointerdown事件
container.addEventListener('pointerdown', function (e) {
this.setPointerCapture(e.pointerId);
isPointerDown = true;
// 停止动画
cancelAnimationFrame(rafId);
point = { x: e.clientX, y: e.clientY };
last = { x: e.clientX, y: e.clientY };
diff = { x: 0, y: 0 };
});
// 监听pointermove事件
container.addEventListener('pointermove', function (e) {
if (isPointerDown) {
const current = { x: e.clientX, y: e.clientY };
// 计算相对于上一次移动差值
diff = { x: current.x - last.x, y: current.y - last.y };
// 旋转角度,乘以0.1是为了降低旋转敏感度,防止旋转过快。可自行设置合适的值
rotate.x -= diff.y * 0.1;
rotate.y += diff.x * 0.1;
last = { x: current.x, y: current.y };
wrap.style.transform = 'rotateX(' + rotate.x + 'deg) rotateY(' + rotate.y + 'deg)';
}
});
// 监听pointerup事件
container.addEventListener('pointerup', function (e) {
isPointerDown = false;
// 惯性滚动
raf();
});
// 监听pointercancel事件
container.addEventListener('pointercancel', function (e) {
isPointerDown = false;
});
上述代码使用PointerEvent指针事件实现。如果对指针事件还不太了解的小伙伴,请看这篇文章。js PointerEvent指针事件简单介绍。
惯性滚动
function raf() {
// ES6解构赋值
let { x, y } = diff;
function step() {
// 摩擦力
x *= 0.95;
y *= 0.95;
rotate.x -= y * 0.1;
rotate.y += x * 0.05;
wrap.style.transform = 'rotateX(' + rotate.x + 'deg) rotateY(' + rotate.y + 'deg)';
// 小于1停止动画
if (Math.abs(x) > 1 || Math.abs(y) > 1) {
rafId = requestAnimationFrame(step);
}
}
rafId = requestAnimationFrame(step);
}