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

news2024/11/20 13:43:10

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();
    // const
    directionHandle(
        () => {
            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.coloradmin.cn/o/127745.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

figma和sketch应该选择哪个?

设计行业的工具层出不穷&#xff0c;在我看来sketch它在一定程度上被颠覆了PS&#xff0c;如今sketch已经成为许多设计团队的设计工具。 那么Figma相对于Sketch自身优势是什么&#xff1f;有什么不便&#xff1f;让我们从几个方面来了解。 两个软件都很适合创建UI和组件库。Sk…

图形查看器丨IrfanView功能简介

IrfanView 是一款快速、紧凑和创新的图形查看器&#xff0c;适用于Windows XP、Vista、7、8、10和11。 IrfanView寻求创建独特、新颖和有趣的功能&#xff0c;与其他一些图形查看器不同&#xff0c;它们的全部“创造力”是基于功能克隆、窃取想法和来自ACDSee和/或IrfanView的整…

mac vscode安装dart

1.安装Dart 1.安装下载Dart的工具 官网&#xff1a;https://brew.sh/ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"如果遇到 zsh: command not found: brew /bin/zsh -c "$(curl -fsSL https://gite…

Vue.js

文章目录1、vue核心基础1.1、安装1.2、Hello Vue1.3、模板语法1.4、数据绑定1.5、el与data的两种写法1.6、理解MVVM模型1.7、Object.defineProperty方法1.8、数据代理1.9、事件处理2.0、事件修饰符2.1、键盘事件2.2、计算属性2.3、监视属性2.4、绑定样式2.5、条件渲染2.6、列表…

《计算机网络》——第五章知识点

可靠 保证接收方进程从缓存区读出的字节流与发送方发出的字节流是完全一样的。 确认重传不分家&#xff0c;TCP的发送方在规定的时间内没有收到确认就要重传已发送的报文段。 流量控制:让发送方慢点&#xff0c;要让接收方来得及接收。 TCP利用滑动窗口机制实现流量控制。 …

PCB电磁兼容设计1

PCB电磁兼容设计 20221229 关键元件 无源器件 模拟、逻辑器件 磁性元件 开关元件 连接器元件 多数情况&#xff0c;电路基本元件满足EMC程度将决定设备满足EMC的程度。 实际元件不是“理想”的&#xff0c;本身可能是干扰源或敏感设备。 选择合适的电子元件的主要准则包…

try/catch捕获不到的异常

try/catch捕获不到的异常捕获不到的异常这种情况finally块会执行吗&#xff1f;spring中的Transactional事务还会会滚吗&#xff1f;该如何捕获这种异常&#xff1f;Throwable可以看做是异常世界中的Object&#xff0c;在Java中所有异常都有一个共同的祖先&#xff1a;Throwabl…

Python 并行加速技巧分享

文章目录一、 使用joblib进行并行计算二、使用Parallel与delayed进行并行加速一、 使用joblib进行并行计算 作为一个被广泛使用的第三方Python库&#xff08;譬如scikit-learn项框架中就大量使用joblib进行众多机器学习算法的并行加速&#xff09;&#xff0c;我们可以使用pip…

【十天成为红帽工程师】第八天 学习编写playbook

目录 一、playbook编写要素 二、playbook编写前的准备 三、实验要求操作 一、playbook编写要素 &#xff08;一&#xff09;playbook位置可这样写&#xff1a;/ansible/chap1/play1.yml 文件后缀为.yml&#xff0c;以yaml格式编写的文本文件 文档开头标记--- 文档结束标…

加解密与HTTPS(2)

您好&#xff0c;我是湘王&#xff0c;这是我的CSDN博客&#xff0c;欢迎您来&#xff0c;欢迎您再来&#xff5e; 近些天由于完全放开&#xff0c;我也从“杨过”变成了“杨康”&#xff0c;加之家中亲人故去&#xff0c;所以长久未能更新&#xff0c;特此致歉&#xff5e; 上…

利用python实现热力学地图(保姆式讲解)

一、首先展示最终的效果 对二手房房源的分析,将分析后的结果用热力图显示: 显示效果如下所示&#xff1a; heatMap参考代码如下&#xff1a; <!DOCTYPE html> <html lang"en"> <head> <!DOCTYPE html> <head><meta http-equiv&q…

一起看跨年烟花(流行背景音乐+雪花)---- 系列

2023年快要到来啦&#xff0c;很高兴这次我们又能一起度过~ 目录 一、前言 二、跨年烟花 三、效果展示 四、详细介绍 五、编码实现 index.html js 六、获取代码 需要源码&#xff0c;可以私信我(⊙o⊙)&#xff1f;关注我&#xff1f; 一、前言 时光荏苒&#xff0c;白…

数据结构-树

1、树的分类 &#xff08;1&#xff09;满二叉树 一个二叉树&#xff0c;如果每一个层的结点数都达到最大值&#xff0c;则这个二叉树就是满二叉树。也就是说&#xff0c;如果一个二叉树的层数为K&#xff0c;且结点总数是(2^k)-1&#xff0c;则它就是满二叉树。 &#xff08;2…

ThingsKit物联网平台 v1.0.1-Release版本发布

基于ThingsBoard二次开发的物联网平台推荐&#xff1a;ThingsKit物联网平台&#xff0c;开箱即用的物联网低代码平台&#xff0c;提供N1N的产品服务体系&#xff0c;帮助企业快速搭建稳定可靠的物联网系统平台&#xff0c;为企业节省大量时间及人力成本。www.thingskit.com Th…

centos7安装k3s和rancher

文章目录一. 安装k3s1.1 关闭防火墙1.2 修改hostname1.3 安装containerd1.4 containerd安装mysql81.5 安装k3s1.6 卸载k3s二. 安装rancher2.1 安装helm2.1.1 下载2.1.2 安装2.1.3 添加几个repo2.1.4 报错2.2 helm安装ingress-nginx2.3 添加rancher repo2.4 helm安装rancher(自己…

读论文---Clip微调---CLIP Itself is a Strong Fine-tuner

标题 摘要 Recent studies have shown that CLIP has achieved remarkable success in performing zero-shot inference while its fine-tuning performance is not satisfactory. In this paper, we identify that fine-tuning performance is significantly impacted by hyp…

复杂并发场景下的并发调度模型在转转的演进之路

文章目录一、问题背景二、复杂并发场景释义2.1 简单并发场景2.2 复杂并发场景三、分组并发调度模型演进3.1 简单异步并发调度3.2 分组并发调度四、自驱动并发调度模型演进4.1 一个优化耗时的小目标及其实现4.2 下一步的疑惑4.3 对问题的重新思考以及自驱动并发调度模型的诞生4.…

蓝桥集训(附加面试题)第八天

本文来源于算法面试题特训专栏&#xff0c;这里有大量专业性的算法题比如&#xff08;动态规划21天&#xff0c;大厂特训28天等等&#xff09; 欢迎大家一起学习。 链接&#xff1a;传送门 目录标题导读Java蓝桥集训面试题点击直接资料领取导读 在刚刚结束的 每日算法&面…

【pandas】教程:3-取DataFrame子集

pandas 取 DataFrame 的子集 pandas 选择列 注&#xff1a; 引用库的导入和数据的导入只做一次&#xff0c;所有代码是在 jupyter notebook 里完成的。 import pandas as pd titanic pd.read_csv("data/titanic.csv")只要年龄数据 ages titanic["Age"…

速度杠杠的,部署机器学习模型的这7个要点要谨记

在模型部署时&#xff0c;模型的性能和耗时都非常重要。但是我们在构建模型时&#xff0c;往往没有考虑模型的预测速度。虽然性能优化会损害预测准确性,但更简单的模型通常运行得更快&#xff0c;也不容易过拟合。 预测延迟被测量为进行预测所需的经过时间。延迟通常被视为一个…