CSS 奇技淫巧Box-shadow实现圆环进度条

news/2025/2/19 8:16:54/

CSS 奇技淫巧Box-shadow实现圆环进度条.png

CSS 奇技淫巧Box-shadow实现圆环进度条

文章目录

  • CSS 奇技淫巧Box-shadow实现圆环进度条
    • 一、Box-shadow圆环进度条
    • 二、效果预览
    • 三、原理刨析
    • 四、实际应用
    • 五、总结
    • 六、参考资料💘
    • 七、推荐博文🍗


一、Box-shadow圆环进度条

实现圆环进度条的方法用很多种,比较容易想到的可能是通过 border属性实现,在本文将使用 Box-shadow盒子阴影呈现,一般来说还真的难想到这个方法,说这种方法是一个奇技淫巧也不为过,让我们接着来看。


二、效果预览

<div class="container"><div class="ring-wrap"><div class="ring">Hover</div></div>
</div>
$borderColor: #ff5d8f;// 设置阴影
@function setShadow($x: 0, $y: 0, $fuzzy: 0, $spread: 0, $color: $borderColor) {@return #{$x}px #{$y}px #{$fuzzy}px #{$spread}px $color;
}.container {display: flex;background-color: #6C6C6C;height: 500px;
}.ring-wrap {display: flex;overflow: hidden;width: 156px;height: 156px;margin: auto;border-radius: 50%;.ring {// 宽高需要预留边框大小width: 150px;height: 150px;line-height: 150px;margin: auto;border-radius: 50%;font-size: 25px;text-align: center;color: #fff;box-shadow: setShadow(75, -75, $color: transparent), setShadow(-75, -75), setShadow(75, -75), setShadow(-75, 75), setShadow(75, 75);background-color: #2894FF;cursor: pointer;&:hover {animation: ring-border 2s ease-in-out forwards;}
}
}@keyframes ring-border {0% {box-shadow: setShadow(75, -75, $color: transparent), setShadow(-75, -75), setShadow(75, -75), setShadow(-75, 75), setShadow(75, 75), setShadow($spread: 3, $color: transparent);}25% {box-shadow: setShadow(75, -75, $color: transparent), setShadow(-75, -75), setShadow(0, -160), setShadow(-75, 75), setShadow(75, 75), setShadow($spread: 3, $color: #FFF);}50% {box-shadow: setShadow(75, -75, $color: transparent), setShadow(-160, 0), setShadow(0, -160), setShadow(-75, 75), setShadow(75, 75), setShadow($spread: 3, $color: #FFF);}75% {box-shadow: setShadow(75, -75, $color: #fff), setShadow(-160, 0), setShadow(0, -160), setShadow(0, 160), setShadow(75, 75), setShadow($spread: 3, $color: #FFF);}100% {box-shadow: setShadow(75, -75, $color: #fff), setShadow(-160, 0), setShadow(0, -160), setShadow(0, 160), setShadow(160, 0), setShadow($spread: 3, $color: #FFF);}
}

2.1 悬浮动画.gif


三、原理刨析

原理很简单,最重要的是控制阴影按照顺序延时移动,除此之外,还需要一层父元素使用 overflow:hidden 对额外的阴影进行隐藏,父子宽度高不能一致,需要留足阴影填充边框的间隙。

3.1 悬浮动画.gif

特别注意的是box-shadow属性使用逗号进行分割多个值,每个值的顺序并不是固定的,其意义简单干脆,就是为元素设置多个不同的阴影。
现在网上很多教程往往在注释或则文中为每个值表明上下左右,为每个值表明顺序,但其实就是不同阴影的xy轴位置不同,本意上是为了标识每个阴影的位置,但好心办坏事造成新手固有思维,不知道的话千万别被误导了

.ring-wrap {// .......ring {// ......box-shadow:// 左上setShadow(-75, -75),// 右上setShadow(75, -75),// 左下setShadow(-75, 75),// 右下setShadow(75, 75);// ......}
}

使用Box-shadow实现圆环进度条,其实使用四个阴影之间的移动即可完成,网上很多教程是这样,最初的设想也是这样,但最后的效果有点出乎意料。以下是最初的样式。

$borderColor: #ff5d8f;// 设置阴影
@function setShadow($x: 0, $y: 0, $fuzzy: 0, $spread: 0, $color: $borderColor) {@return #{$x}px #{$y}px #{$fuzzy}px #{$spread}px $color;
}.container {display: flex;background-color: #6C6C6C;height: 500px;
}.ring-wrap {display: flex;overflow: hidden;width: 156px;height: 156px;margin: auto;border-radius: 50%;.ring {// 宽高需要预留边框大小width: 150px;height: 150px;line-height: 150px;margin: auto;border-radius: 50%;font-size: 25px;text-align: center;color: #fff;box-shadow: setShadow(-75, -75), setShadow(75, -75), setShadow(-75, 75), setShadow(75, 75);background-color: #2894FF;cursor: pointer;&:hover {animation: ring-border 5s ease-in-out forwards;}}
}@keyframes ring-border {0% {box-shadow: setShadow(-75, -75), setShadow(75, -75), setShadow(-75, 75), setShadow(75, 75), setShadow($spread: 3, $color: transparent);}25% {box-shadow: setShadow(-75, -75), setShadow(0, -160), setShadow(-75, 75), setShadow(75, 75), setShadow($spread: 3, $color: #FFF);}50% {box-shadow: setShadow(-160, 0), setShadow(0, -160), setShadow(-75, 75), setShadow(75, 75), setShadow($spread: 3, $color: #FFF);}75% {box-shadow: setShadow(-160, 0), setShadow(0, -160), setShadow(0, 160), setShadow(75, 75), setShadow($spread: 3, $color: #FFF);}100% {box-shadow: setShadow(-160, 0), setShadow(0, -160), setShadow(0, 160), setShadow(160, 0), setShadow($spread: 3, $color: #FFF);}
}

3.2 悬浮动画.gif
为了解决这个问题,通过新增一个阴影进行改进越前书写的阴影其优先级越高,通过在前书写的阴影覆盖移除的阴影移动,覆盖的阴影要在动画进行到75%或之前完成覆盖以实现效果
3.2 悬浮动画.gif

点击查看【juejin】

ps:使用码上掘金线上代码编辑器查看效果~


四、实际应用

在悬浮上展示进度条的场景估计很少,一般在与用户交互的场景下使用的会多些。

说了这么多也没有实际用用上,并不知道实际好坏,那咱们简单编写一个轮播图场景进行应用,看看实际效果如何。

<div class="container"><div class="swipe"><div class="swipe-img"><img src="https://w.wallhaven.cc/full/1p/wallhaven-1p398w.jpg" alt class="active"><img src="https://w.wallhaven.cc/full/7p/wallhaven-7p3we9.png" alt><img src="https://w.wallhaven.cc/full/rr/wallhaven-rr2yow.jpg" alt></div><div class="swipe-btn"><div class="left-btn"><b class="btn"> < </b></div><div class="right-btn"><b class="btn"> > </b></div></div></div></div>
const opts = {// 控制延迟interval: 3000,// 控制方向direction: "right",_indexImg: 0,
};const leftBtn = document.querySelector(".left-btn .btn");
const rightBtn = document.querySelector(".right-btn .btn");
const imgList = document.querySelectorAll(".swipe-img img");// 获取激活图片索引
function getImgIndex() {for (let index in imgList) {const item = imgList[index];if (Array.from(item.classList).includes("active")) {return index;}}return 0;
}/*** 不同方向处理* @param {Function} left 左方向处理回调* @param {Function} right 右方向处理回调*/
function directionHandle(left, right) {if (/^left$/i.test(opts.direction)) {left();} else {right();}
}function switchSwipe(direction = "auto") {imgList[opts._indexImg]?.classList?.remove?.("active");switch (true) {case /^auto$/i.test(direction):directionHandle(() => opts._indexImg--,() => opts._indexImg++);break;case /^left$/i.test(direction):opts._indexImg--;break;default:opts._indexImg++;}switch (true) {case opts._indexImg > imgList.length - 1:opts._indexImg = 0;break;case opts._indexImg < 0:opts._indexImg = imgList.length - 1;break;}imgList[opts._indexImg]?.classList.add("active");
}function autoPlay() {opts._indexImg = getImgIndex();// constdirectionHandle(() => {leftBtn.style.animationDuration = `${opts.interval / 1000}s`;leftBtn.classList.add("active");},() => {rightBtn.style.animationDuration = `${opts.interval / 1000}s`;rightBtn.classList.add("active");});return setInterval(() => switchSwipe(), opts.interval);
}function execute() {// 清除自动播放辅助函数const clearAuto = (atimer, dtimer) => {atimer && clearInterval(atimer);dtimer && clearTimeout(dtimer);leftBtn.classList.remove("active");rightBtn.classList.remove("active");};let [autoTimer, delayTimer] = [autoPlay(), null];leftBtn.addEventListener("click", () => {clearAuto(autoTimer, delayTimer);switchSwipe("left");delayTimer = setTimeout(() => {timer = autoPlay();}, opts.interval);});rightBtn.addEventListener("click", () => {clearAuto(autoTimer, delayTimer);switchSwipe("right");delayTimer = setTimeout(() => {timer = autoPlay();}, opts.interval);});
}execute();
$borderColor: #ff5d8f;// 设置阴影
@function setShadow($x: 0, $y: 0, $fuzzy: 0, $spread: 0, $color: $borderColor) {@return #{$x}px #{$y}px #{$fuzzy}px #{$spread}px $color;
}.swipe {position: relative;width: 100%;height: 350px;display: flex;&-img {img {display: none;width: 100%;height: 100%;position: absolute;top: 0;left: 0;object-fit: cover;}.active {display: block;}}&-btn {font-size: 30px;color: #fff;.left-btn, .right-btn {display: flex;position: absolute;overflow: hidden;transform: translateY(-50%);top: 50%;width: 54px;height: 54px;line-height: 45px;text-align: center;border-radius: 50%;background-color: rgba(0, 0, 0, .5);cursor: pointer;.active {animation: swipe-btn ease-in-out infinite forwards;}}.left-btn {left: 1%;}.right-btn {right: 1%;}.btn {width: 50px;height: 50px;margin: auto;border-radius: 50%;box-shadow: setShadow(25, -25, $color: transparent), setShadow(-25, -25), setShadow(25, -25), setShadow(-25, 25), setShadow(25, 25);}}
}@keyframes swipe-btn {0% {box-shadow: setShadow(25, -25, $color: transparent), setShadow(-25, -25), setShadow(25, -25), setShadow(-25, 25), setShadow(25, 25), setShadow($spread: 2, $color: transparent);}25% {box-shadow: setShadow(25, -25, $color: transparent), setShadow(-25, -25), setShadow(0, -60), setShadow(-25, 25), setShadow(25, 25), setShadow($spread: 2, $color: #FFF);}50% {box-shadow: setShadow(25, -25, $color: transparent), setShadow(-60, 0), setShadow(0, -60), setShadow(-25, 25), setShadow(25, 25), setShadow($spread: 2, $color: #FFF);}75% {box-shadow: setShadow(25, -25, $color: #fff), setShadow(-60, 0), setShadow(0, -60), setShadow(0, 60), setShadow(25, 25), setShadow($spread: 2, $color: #FFF);}100% {box-shadow: setShadow(25, -25, $color: #fff), setShadow(-60, 0), setShadow(0, -60), setShadow(0, 60), setShadow(60, 0), setShadow($spread: 2, $color: #FFF);}
}

4.1 轮播动画.gif

点击查看【juejin】

ps:使用码上掘金线上代码编辑器查看效果~


五、总结

其实使用Box-shadow进行实现的关键点在于控制阴影按照顺序延时移动,移动的越快速度则越快,反则越慢。为解决最后一阴影移动便宜的问题,需要新增一个优先级高的阴影提前进行覆盖,当然嫌麻烦的话可以不用。

优缺点:

  • 因为是移动四个不同的阴影来控制进度,在阴影的切换处很明显会有顿挫感,对于需要平滑进度条的场景来说不太适用,但对于需要顿挫感的场景来说又很适用,可谓是一把双刃剑,关键要看在哪里用。
  • 使用阴影控制圆环进度条,这个方法是比较难想到的,实现起来还需要一层父元素,编写起来需要一定的熟练度。
  • 兼容性方面会强一些,只要浏览器支持animation动画,大多数可以实现。

另外值得一提的是,在一些 UI组件库中,环形进度条一般是已经被封装好的,直接拿来用即可,以下图element ui为例。
image.png


六、参考资料💘

  • 官方手册:
    • MDN: https://developer.mozilla.org/zh-CN/docs/Web/CSS/box-shadow
  • 网络文献:
    • CSS灵感: https://chokcoco.github.io/CSS-Inspiration/#/./shadow/circle-loading
  • 相关连接:
    • element-ui: https://element-plus.gitee.io/zh-CN/component/progress.html

七、推荐博文🍗

  • JavaScript 灵活使用Console
  • 【精】Vue 使用props为路由组件传参『详解』

http://www.ppmy.cn/news/8440.html

相关文章

【Mongoose笔记】Websocket 服务器

【Mongoose笔记】Websocket 服务器 简介 Mongoose 笔记系列用于记录学习 Mongoose 的一些内容。 Mongoose 是一个 C/C 的网络库。它为 TCP、UDP、HTTP、WebSocket、MQTT 实现了事件驱动的、非阻塞的 API。 项目地址&#xff1a; https://github.com/cesanta/mongoose学习 …

shell第五天作业——函数与数组

题目 一、编写函数&#xff0c;实现打印绿色OK和红色FAILED 二、编写函数&#xff0c;实现判断是否有位置参数&#xff0c;如无参数&#xff0c;提示错误 三、编写函数实现两个数字做为参数&#xff0c;返回最大值 四、编写函数&#xff0c;实现两个整数为参数&#xff0c;…

windows11 wsl kali子系统 adb调试

环境准备 在Windows中 安装usbipd 参考资料&#xff1a;https://learn.microsoft.com/zh-cn/windows/wsl/connect-usb 方法一&#xff1a; 在powershell中执行winget install --interactive --exact dorssel.usbipd-win 方法二&#xff1a;去https://github.com/dorssel/usbi…

从umati 看德国人如何玩OPCUA的

到目前为止&#xff0c;机器的联网标准缺乏统一的协议和语义标准。比较知名的要数每个的MTConnect。fanuc机床的focas协议。未来的发展方向是OPCUA协议。但是实现这个目标并非一日之功。德国的umati 社区也许给我们一些启发。 为了推进机床行业的数字化进程&#xff0c;VDW&…

Good Bye 2022: 2023 is NEAR

A.B略 C.鸽巢原理中国剩余定理 首先,显然所有数必须两两相同,不然加上任何整数必定会产生gcd > 2的组合 然后我们手玩可以发现奇偶性,我们至少不能使得有两个数同时是偶数,如果出现两个偶数,两个奇数,例如2 4 5 7,那么无论x选择什么,都至少会出现两个偶数,进一步地,我们发…

Linux多进程编程之exec函数族使用

Linux多进程编程之exec函数族使用1.exec函数族是什么2.execl函数具体使用3.execlp4.exec后面不同字母所代表的含义1.exec函数族是什么 顾名思义&#xff0c;它并不只是一个函数&#xff0c;而是以exec开头的六个函数&#xff0c;并且是没有exec这个函数的&#xff08;就像TCP/…

C++中STL的vector扩容机制

目录前言发生扩容扩容机制size()和capacity()reserve()和resize()前言 前阵子面试的时候&#xff0c;被问到往vector中插入一个数据可能会发生什么&#xff1f; 我答:可能会扩容; 为啥vector支持变长&#xff1f; 我答:它实在堆上动态申请内存&#xff0c;因此有自己的一套扩容…

保护性暂停设计模式

目录 保护性暂停设计模式 获取结果 产生结果 总代码实现 测试 增加超时效果的Guarded suspension get(long timeout) 测试 保护性暂停设计模式 Guarded Suspension 即 保护性暂停; 是一种等待唤醒机制的一种规范 ,也可以理解为使用中设计模式,Java的API很多都按照保护性…