说在前面
这种碎片化的图片过渡效果大家应该都见过吧,不过主要还是在视频剪辑效果中比较多见,今天我们一起来看看怎么在前端页面中实现一下这个图片碎片化的过渡效果,并用这个过渡动画做一个轮播图。
在线体验
码上掘金
codePen
核心代码实现
html部分
<div class="container">
<div class="carousel-container" id="carousel-container">
<div class="carousel" id="carousel">
</div>
<button class="nav-btn prev" id="prev-btn">
<span>‹</span>
</button>
<button class="nav-btn next" id="next-btn">
<span>›</span>
</button>
<div class="indicators" id="indicators">
</div>
</div>
</div>
html中定义好基础容器,包括轮播图区域、左右导航按钮和底部指示器,具体的图片和指示器通过 JS 动态生成。
轮播图配置
const carouselConfig = {
images: [
{
src: "http://5b0988e595225.cdn.sohucs.com/images/20180130/24aecf0d680546ccbf6480badce389dc.jpeg",
alt: "千与千寻",
},
…………
],
fragmentConfig: {
rows: 10, // 碎片行数
cols: 15, // 碎片列数
maxDisplacement: 400, // 碎片最大位移距离
maxRotation: 180, // 碎片最大旋转角度
minScale: 0.3, // 碎片最小缩放比例
},
autoplay: true, // 是否自动播放
autoplaySpeed: 5000, // 自动播放速度(毫秒)
transitionDuration: 800, // 过渡动画持续时间(毫秒)
};
- images:轮播图片数组,直接在这里修改需要轮播展示的图片
- fragmentConfig:碎片化效果参数,最终的碎片化效果根据这里的配置参数来生成
- autoplay:自动轮播开关,可以配置是否开启自动轮播
- autoplaySpeed:自动轮播速度,可以调节轮播速度
- transitionDuration:碎片化过渡动画持续时间(毫秒)
轮播图初始化
function initCarousel() {
carouselConfig.images.forEach((image, index) => {
const slide = document.createElement("div");
slide.className = `slide ${index === 0 ? "active" : ""}`;
slide.dataset.index = index;
const img = document.createElement("img");
img.src = image.src;
img.alt = image.alt;
slide.appendChild(img);
carouselContainer.appendChild(slide);
// 创建指示器
const dot = document.createElement("button");
dot.className = `dot ${index === 0 ? "active" : ""}`;
dot.dataset.index = index;
dot.addEventListener("click", () => goToSlide(index));
indicators.appendChild(dot);
});
// 设置自动播放
if (isPlaying) {
startAutoplay();
}
prevBtn.addEventListener("click", () => {
if (!isTransitioning) goToPrevSlide();
});
nextBtn.addEventListener("click", () => {
if (!isTransitioning) goToNextSlide();
});
}
根据轮播图配置信息来初始化轮播图,动态生成轮播图片元素和指示器,减少html中的重复代码,也方便后续调整轮播图配置。
碎片化动画实现
1、清理旧碎片
在生成新碎片前,首先需要清除上一次动画残留的碎片元素。
const existingFragments = carouselContainer.querySelectorAll(".fragment");
existingFragments.forEach((frag) => frag.remove());
2、图片尺寸计算
为后续生成图片碎片做准备,为了让碎片精准对应原图位置,需要先计算图片的实际显示尺寸。
// 获取图片原始尺寸和容器尺寸
const imgWidth = img.naturalWidth;
const imgHeight = img.naturalHeight;
const containerWidth = carouselContainer.offsetWidth;
const containerHeight = carouselContainer.offsetHeight;
// 计算宽高比
const containerRatio = containerWidth / containerHeight;
const imgRatio = imgWidth / imgHeight;
// 计算实际显示尺寸(模拟object-fit: cover效果)
let displayWidth, displayHeight;
if (imgRatio > containerRatio) {
// 图片更宽:高度填满容器,宽度按比例缩放
displayHeight = containerHeight;
displayWidth = displayHeight * imgRatio;
} else {
// 图片更高:宽度填满容器,高度按比例缩放
displayWidth = containerWidth;
displayHeight = displayWidth / imgRatio;
}
3、生成图片碎片
根据前面配置好的碎片化效果参数来生成碎片,核心技巧是使用 background-position 属性,通过调整背景图的位置,让每个碎片恰好显示原图的对应部分,从而拼接出完整图片。
const { rows, cols, maxDisplacement, maxRotation, minScale } = carouselConfig.fragmentConfig;
const fragmentWidth = displayWidth / cols; // 单个碎片宽度
const fragmentHeight = displayHeight / rows; // 单个碎片高度
// 循环生成所有碎片
for (let i = 0; i < rows; i++) { // 行循环
for (let j = 0; j < cols; j++) { // 列循环
const fragment = document.createElement("div");
fragment.className = "fragment";
// 设置碎片位置和大小
fragment.style.left = `${j * fragmentWidth - offsetX}px`;
fragment.style.top = `${i * fragmentHeight - offsetY}px`;
fragment.style.width = `${fragmentWidth}px`;
fragment.style.height = `${fragmentHeight}px`;
// 关键:通过背景图定位显示原图对应区域
fragment.style.backgroundImage = `url(${img.src})`;
fragment.style.backgroundSize = `${displayWidth}px ${displayHeight}px`;
fragment.style.backgroundPosition = `-${j * fragmentWidth}px -${i * fragmentHeight}px`;
carouselContainer.appendChild(fragment);
}
}
4、碎片动画
生成图片碎片之后我们需要让碎片有一个飘散动画,直接随机生成不同的位移、旋转和缩放值,然后每个碎片延迟错开触发。
setTimeout(() => {
// 计算随机位移、旋转和不透明度
const randomX = (Math.random() - 0.5) * maxDisplacement;
const randomY = (Math.random() - 0.5) * maxDisplacement;
const randomRotation = (Math.random() - 0.5) * maxRotation;
const randomScale = minScale + Math.random() * (1 - minScale);
fragment.style.transform = `translate(${randomX}px, ${randomY}px) rotate(${randomRotation}deg) scale(${randomScale})`;
fragment.style.opacity = "0";
}, (i * cols + j) * 10);
5、碎片清理并执行回调
碎片动画结束后需要将碎片移除,并执行回调通知动画完成。
const totalDuration =
carouselConfig.transitionDuration + rows * cols * 10;
setTimeout(() => {
const fragments = carouselContainer.querySelectorAll(".fragment");
fragments.forEach((frag) => frag.remove());
if (callback) callback();
}, totalDuration);
轮播图切换逻辑
1、状态检测,防止冲突操作
if (index === currentSlide || isTransitioning) return;
如果切换的图片就是当前正在显示的图片或者过渡动画未执行完成时,不执行图片切换操作。
2、碎片过渡动画
isTransitioning = true; // 锁定过渡状态
const slides = document.querySelectorAll(".slide");
const currentSlideEl = slides[currentSlide];
const newSlideEl = slides[index];
// 碎片化当前轮播图
fragmentImage(currentSlideEl.querySelector("img"), () => {
// 隐藏当前轮播图,显示新轮播图
currentSlideEl.classList.remove("active");
newSlideEl.classList.add("active");
currentSlide = index;
// 重置自动播放计时器
if (isPlaying) resetAutoplay();
// 解除锁定
isTransitioning = false;
});
调用前面编写的碎片化过渡动画方法将当前的图片碎片化。
3、切换新图片
currentSlideEl.style.opacity = "0";
newSlideEl.style.opacity = "1";
currentSlide = index;
// 更新指示器
document.querySelectorAll(".dot").forEach((dot, i) => {
dot.classList.toggle("active", i === index);
});
碎片动画执行的同时,通过透明度变化实现新旧图片的切换。
源码
gitee
github
- 🌟 觉得有帮助的可以点个 star~
- 🖊 有什么问题或错误可以指出,欢迎 pr~
- 📬 有什么想要实现的功能或想法可以联系我~
公众号
关注公众号『 前端也能这么有趣 』,获取更多有趣内容。
发送 加群 还可以加入群聊,一起来学习(摸鱼)吧~
说在后面
🎉 这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『
前端也能这么有趣』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。