Html5 轮播图 - 从零手搓 纯原生 JavaScript CSS 实现 home 编辑时间 2023/08/01 ![](/api/file/getImage?fileId=64c8d7d9da74050014005ab7) ## 前言 最近发现用现成的前端图形框架,多多少少有点别扭 比如加载文件多速度慢,调整css会有少许冲突 百度搜索结果不出图,因为百度个ZZ要图片在img里src才能识别 等等 <br> 所以是想把线上项目的前端部分重构成几乎纯手搓的方案 最多能接受依赖vue,其余部分全用最简化的代码纯手搓 <br> 理想很美好但水平很有限,辣么多组件,搓到天荒地老也搓不完 真有这本事直接自己写个开源图形框架了到时候 路要一步步走,先从轮播图开始搓,本文简单讲述下过程和原理 <br> PS:其实百度上也有很多前辈的手搓代码可以借鉴,不过我还是尽量自己来 <br> 先贴一下最终效果 大概用了100行代码实现的简易轮播图 <br> **最终源码开源地址** [https://github.com/zzzmhcn/carousel](https://github.com/zzzmhcn/carousel) [https://gitee.com/zzzmhcn/carousel](https://gitee.com/zzzmhcn/carousel) <br> **最终效果在线演示** [https://zzzmhcn.gitee.io/carousel/](https://zzzmhcn.gitee.io/carousel/) <br> **最终效果GIF图** ![carousel](https://cdn1.zzzmh.cn/v2/public/gif/carousel.webp) <br> ## 折腾 原理是参考了CSDN的一位前辈的方案 先把图片横过来排一排 再用 `overflow: hidden` 隐藏超出范围的部分 横移的效果js控制用css3的 `transform: translateX(-100%)` 最后动画用css3的 `transition: transform .5s ease;` 顺便为了减少依赖,2个箭头用的iconfont阿里官方库里下的`svg` 实践一遍大概是这样 1. 先把图片横过来排一排 用flex实现 ```html <head> ... <style> .carousel { display: flex; } .carousel img { width: 100%; display: flex; justify-content: flex-start; } </style> </head> <body> <div class="carousel"> <img src="img/1.webp"> <img src="img/2.webp"> <img src="img/3.webp"> <img src="img/4.webp"> <img src="img/5.webp"> </div> </body> ``` 效果图 ![](/api/file/getImage?fileId=64c9c20dda74050014005b5c) <br> 2. 再把超出部分隐藏 这样就只能看到第一张图 (这里可以用max-width限制宽度,也可以以后在外层div根据显示器自适应宽度,需要注意图片宽度必须大于div的width否则会穿帮) ```html <head> ... <style> .carousel { max-width: 800px; display: flex; overflow: hidden; } .carousel img { width: 100%; display: flex; justify-content: flex-start; } </style> </head> <body> ... </body> ``` 效果图 ![](/api/file/getImage?fileId=64c9c290da74050014005b5d) <br> 3. 加2个按钮示意左右切换 (这里是特意用的js去添加dom方式实现,为了以后用轮播图只需要div包img,不需要任何多余代码) ```html <head> ... <style> .carousel { max-width: 800px; display: flex; overflow: hidden; position: relative; } .carousel img { width: 100%; display: flex; justify-content: flex-start; } .carousel .control { position: absolute; width: 100%; height: 100%; } .carousel .control .next, .carousel .control .prev { position: absolute; top: calc(50% - 2rem); cursor: pointer; user-select: none; background: rgba(66, 66, 66, .66); border-radius: 50%; padding: 6px 8px 4px; } .carousel .control .next { right: 1rem; } .carousel .control .prev { left: 1rem; } </style> </head> <body> <div class="carousel"> <img src="img/1.webp"> <img src="img/2.webp"> <img src="img/3.webp"> <img src="img/4.webp"> <img src="img/5.webp"> </div> <script> const init = function () { const carousel = document.querySelector('.carousel'); const control = document.createElement('div'); control.className = 'control'; const prev = document.createElement('div'); prev.className = 'prev'; prev.innerHTML = '<svg t="1690880358537" viewBox="0 0 1024 1024" p-id="2638" width="24" height="24"><path style="fill: whitesmoke;" d="M659.748571 245.272381l-51.687619-51.687619-318.439619 318.585905 318.415238 318.268952 51.712-51.736381-266.703238-266.556952z" p-id="2639"></path></svg>'; const next = document.createElement('div'); next.className = 'next'; next.innerHTML = '<svg t="1690880498698" viewBox="0 0 1024 1024" p-id="2777" width="24" height="24"><path style="fill: whitesmoke;" d="M605.086476 512.146286L338.358857 245.272381l51.760762-51.687619 318.415238 318.585905L390.095238 830.415238l-51.687619-51.736381z" p-id="2778"></path></svg>'; control.appendChild(prev); control.appendChild(next); carousel.appendChild(control); } window.onload = init; </script> </body> ``` 效果图 ![](/api/file/getImage?fileId=64c9c44fda74050014005b64) <br> 4. 最后给2个按钮写上切换方法 (到这个程度已经可以实现无动画瞬间切换了) ```html <head> ... <style> .carousel { max-width: 800px; display: flex; overflow: hidden; position: relative; } .carousel img { width: 100%; display: flex; justify-content: flex-start; } .carousel .control { position: absolute; width: 100%; height: 100%; } .carousel .control .next, .carousel .control .prev { position: absolute; top: calc(50% - 2rem); cursor: pointer; user-select: none; background: rgba(66, 66, 66, .66); border-radius: 50%; padding: 6px 8px 4px; } .carousel .control .next { right: 1rem; } .carousel .control .prev { left: 1rem; } </style> </head> <body> <div class="carousel"> <img src="img/1.webp"> <img src="img/2.webp"> <img src="img/3.webp"> <img src="img/4.webp"> <img src="img/5.webp"> </div> <script> const init = function () { const carousel = document.querySelector('.carousel'); const size = carousel.querySelectorAll('img').length; const prevImage = function () { const index = Number(carousel.getAttribute('index')) < 1 ? size - 1 : Number(carousel.getAttribute('index')) - 1; carousel.setAttribute('index', String(index)); document.querySelectorAll('.carousel img').forEach(img => { img.style.transform = 'translateX(' + (index * -100) + '%)'; }); }; const nextImage = function () { const index = Number(carousel.getAttribute('index')) >= size - 1 ? 0 : Number(carousel.getAttribute('index')) + 1; carousel.setAttribute('index', String(index)); document.querySelectorAll('.carousel img').forEach(img => { img.style.transform = 'translateX(' + (index * -100) + '%)'; }) }; const control = document.createElement('div'); control.className = 'control'; const prev = document.createElement('div'); prev.className = 'prev'; prev.innerHTML = '<svg t="1690880358537" viewBox="0 0 1024 1024" p-id="2638" width="24" height="24"><path style="fill: whitesmoke;" d="M659.748571 245.272381l-51.687619-51.687619-318.439619 318.585905 318.415238 318.268952 51.712-51.736381-266.703238-266.556952z" p-id="2639"></path></svg>'; const next = document.createElement('div'); next.className = 'next'; next.innerHTML = '<svg t="1690880498698" viewBox="0 0 1024 1024" p-id="2777" width="24" height="24"><path style="fill: whitesmoke;" d="M605.086476 512.146286L338.358857 245.272381l51.760762-51.687619 318.415238 318.585905L390.095238 830.415238l-51.687619-51.736381z" p-id="2778"></path></svg>'; prev.addEventListener('click', prevImage); next.addEventListener('click', nextImage); control.appendChild(prev); control.appendChild(next); carousel.appendChild(control); } window.onload = init; </script> </body> </html> ``` 效果图 ![carousel-demo](https://cdn1.zzzmh.cn/v2/public/gif/carousel-demo.webp) 5. 最后给个动画效果 非常简单一句话 在img的css里加 `transition: transform .5s ease;` ```css .carousel img { width: 100%; display: flex; justify-content: flex-start; /* 切换动画 改变transform时触发 0.5秒内完成 */ transition: transform .5s ease; } ``` <br><br> **大功告成!!!** <br> **最终效果图** ![carousel](https://cdn1.zzzmh.cn/v2/public/gif/carousel.webp) <br> **2023年8月2日更新** 后续我又补上了2个功能 1. 定时自动播放 2. 锚点精确跳转 没时间解释了,你可以自己点击在线体验地址试一下效果 <br> **在线体验** [https://zzzmhcn.gitee.io/carousel/](https://zzzmhcn.gitee.io/carousel/) <br> **完整代码** ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>轮播图demo</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <style> .carousel { max-width: 800px; display: flex; position: relative; overflow: hidden; } .carousel .control { position: absolute; width: 100%; height: 100%; } .carousel .control .next, .carousel .control .prev { position: absolute; top: calc(50% - 2rem); cursor: pointer; user-select: none; background: rgba(66, 66, 66, .66); border-radius: 50%; padding: 6px 8px 4px; } .carousel .control .next { right: 1rem; } .carousel .control .prev { left: 1rem; } .carousel img { width: 100%; display: flex; justify-content: flex-start; /* 切换动画 改变transform时触发 0.5秒内完成 */ transition: transform .5s ease; } .carousel .control .indicators { position: absolute; right: 0; bottom: 2rem; left: 0; z-index: 2; display: flex; justify-content: center; } .carousel .control .indicators .indicator { flex: 0 1 auto; width: 30px; height: 3px; margin-right: 3px; margin-left: 3px; cursor: pointer; background-color: #fff; background-clip: padding-box; opacity: .5; border-top: 1rem solid transparent; border-bottom: 1rem solid transparent; transition: opacity .5s ease; } </style> </head> <body> <!-- class="carousel" 为必填项 autoplay="true" 为选填项 开启自动播放 timeout="1000" 为选填项 开启自动播放间隔时间 默认5000 --> <div class="carousel" autoplay="true" timeout="3000"> <img src="img/1.webp"> <img src="img/2.webp"> <img src="img/3.webp"> <img src="img/4.webp"> <img src="img/5.webp"> </div> <script> const init = function () { const carousel = document.querySelector('.carousel'); const size = carousel.querySelectorAll('img').length; const autoplay = carousel.getAttribute("autoplay") === "true"; const timeout = carousel.getAttribute("timeout") ? Number(carousel.getAttribute("timeout")) : 5000; const auto = function () { carousel.setAttribute('index', String(Number(carousel.getAttribute('index')) >= size - 1 ? 0 : Number(carousel.getAttribute('index')) + 1)); }; // 每5秒自动切换下一张 let interval = autoplay ? window.setInterval(auto, timeout) : null; const observer = new MutationObserver(function (mutations) { mutations.forEach(e => { // 改为监听 attribute 的方案统一处理变化 if (e.target.className == 'carousel') { const index = e.target.getAttribute('index'); document.querySelectorAll('.carousel img').forEach(img => { img.style.transform = 'translateX(' + (index * -100) + '%)'; }); document.querySelectorAll('.indicator').forEach((indicator, i) => { indicator.style.opacity = (i === Number(index) ? "1" : "0.5"); }); // 数值发生变化自动重新计算 避免闪屏 if(autoplay){ clearInterval(interval); interval = window.setInterval(auto, timeout); } } }); }); observer.observe(carousel, {attributes: true}); // 用js添加控制器模块dom 减少html中代码 const control = document.createElement('div'); control.className = 'control'; const prev = document.createElement('div'); prev.className = 'prev'; prev.innerHTML = '<svg t="1690880358537" viewBox="0 0 1024 1024" p-id="2638" width="24" height="24"><path style="fill: whitesmoke;" d="M659.748571 245.272381l-51.687619-51.687619-318.439619 318.585905 318.415238 318.268952 51.712-51.736381-266.703238-266.556952z" p-id="2639"></path></svg>'; const next = document.createElement('div'); next.className = 'next'; next.innerHTML = '<svg t="1690880498698" viewBox="0 0 1024 1024" p-id="2777" width="24" height="24"><path style="fill: whitesmoke;" d="M605.086476 512.146286L338.358857 245.272381l51.760762-51.687619 318.415238 318.585905L390.095238 830.415238l-51.687619-51.736381z" p-id="2778"></path></svg>'; prev.addEventListener('click', function () { carousel.setAttribute('index', String(Number(carousel.getAttribute('index')) < 1 ? size - 1 : Number(carousel.getAttribute('index')) - 1)); }); next.addEventListener('click', function () { carousel.setAttribute('index', String(Number(carousel.getAttribute('index')) >= size - 1 ? 0 : Number(carousel.getAttribute('index')) + 1)); }); const indicators = document.createElement('div'); indicators.className = 'indicators'; for (let i = 0; i < size; i++) { const indicator = document.createElement('div'); indicator.className = 'indicator'; indicator.addEventListener('click', function () { carousel.setAttribute('index', String(i)); }); indicators.appendChild(indicator); } control.appendChild(prev); control.appendChild(next); control.appendChild(indicators); carousel.appendChild(control); // 初始化index carousel.setAttribute('index', '0'); } window.onload = init; </script> </body> </html> ``` <br> ## END **源码开源地址** [https://github.com/zzzmhcn/carousel](https://github.com/zzzmhcn/carousel) [https://gitee.com/zzzmhcn/carousel](https://gitee.com/zzzmhcn/carousel) <br> 目前完成度还是较低,只是日常够用,存在几个不足 以后有时间有机会再继续补足 1. ~~无操作时,自动播放功能,类似幻灯片~~ 2. 图片懒加载,以及未加载图片自动占位(这条和百度必须图片在img的src里有可能冲突,暂时不做) 3. ~~图片只能左右切换,别人的轮播图有下面一排小黑点可以指定切换哪一张~~ <br> **2023年8月2日更新** (上述问题 1 和 3 已实现并更新到线上) <br> 累了就先这样吧 ![](/api/file/getImage?fileId=64c9c860da74050014005b69) 送人玫瑰,手留余香 赞赏 Wechat Pay Alipay 桌游说明书备忘录 2023 更新 ROG G14 安装 Linux Deepin 入门教程