某日,群里有这样一个问题,如何实现这样的表盘刻度:

这其实是个挺有意思的问题,方法也有很多。

单标签,使用 conic-gradient 实现表盘刻度

最简单便捷的方式,就是利用角向渐变的方式 conic-gradient,代码也非常简单,首先,我们实现一个重复角向渐变:

<div></div>
div {
width: 300px;
height: 300px;
border-radius: 50%;
background: repeating-conic-gradient(
#000 0, #000 .8deg, transparent 1deg, transparent calc((360 / 60) * 1deg)
);
}

其实比较难理解的是 calc((360 / 60) * 1deg),这是因为表盘一共通常有 60 个刻度。效果大概是这样:

接下来,只需要将中间镂空即可。如果背景色是白色,直接叠加一个圆形即可,当然,更好的方式是通过 mask 属性进行镂空:

{
background: repeating-conic-gradient(
#000 0, #000 .8deg, transparent 1deg, transparent calc(360 / 60 * 1deg)
);
mask: radial-gradient(transparent 0, transparent 140px, #000 140px)
}

这样,我们就得到了一个表盘刻度:

这是使用一个标签就能实现的方式,当然,缺点也很明显:

  1. 锯齿感严重,渐变的通病
  2. 由于是使用的角向渐变,刻度存在头重脚轻的现象,越向内部,宽度越窄(刻度愈大,差异愈加明显)

使用多个标签实现

如果不介意使用太多的标签,那么通常而言,更容易想到的方法就是利用 60 个标签,加上旋转实现:

<div class="g-container">
<div class="g-item"></div>
// ... 一共 60 个
<div class="g-item"></div>
</div>
.g-item {
position: absolute;
width: 4px;
height: 12px;
background: #000;
left: 0;
top: 0;
transform-origin: 0 150px;
}
@for $i from 1 through 60 {
.g-item:nth-child(#{$i}) {
transform: rotate(#{($i - 1) * 6deg});
}
}

像是这样,我们通过 60 个 div 标签,利用 SASS 的 for 语法减少重复的代码量,批量实现每个元素逐渐绕一点旋转一定的角度,也是可以实现一个表盘刻度的:

这个方案的好处是,每个刻度粗细一致,并且,不会产生锯齿。

借助 -webkit-box-reflect 减少标签数

当然,上述方案的缺点就在于,使用了 60 个标签去完成这样一个简单的图形,有点太奢侈了。

我们希望尽可能的优化优化标签的个数。此时,我们很容易的想到 -- -webkit-box-reflect,倒影效果属性。

-webkit-box-reflect 是一个非常有意思的属性,它让 CSS 有能力像镜子一样,反射我们元素原本绘制的内容。

-webkit-box-reflect 的语法非常简单,最基本的用法像是这样:

div {
-webkit-box-reflect: below;
}

其中,below 可以是 below | above | left | right 代表下上左右,也就是有 4 个方向可以选。

假设我们有如下一张图片:

<div></div>
div {
background-image: url('https://images.pokemontcg.io/xy2/12_hires.png');
}

加上 -webkit-box-reflect: right,也就是右侧的倒影:

div {
background-image: url('https://images.pokemontcg.io/xy2/12_hires.png');
-webkit-box-reflect: right;
}

效果如下,生成了一个元素右侧的镜像元素:

借助 -webkit-box-reflect: right,我们至少可以从 60 个标签减少到 15 个标签的使用。简单的嵌套两层即可。

我们简单改变一下 HTML 结构:

<div class="g-parent">
<div class="g-container">
<div class="g-item"></div>
// ... 一共 16 个
<div class="g-item"></div>
</div>
</div>

这一次,我们只需要实现 1/4 圆的刻度即可:

@for $i from 1 through 16 {
.g-item:nth-child(#{$i}) {
transform: rotate(#{($i - 1) * 6deg});
}
}

我们可以得到这样一个图形:

基于这个图形,我们只需要先向左侧倒影一次,再向下倒影一次即可:

.g-container {
-webkit-box-reflect: below;
}
.g-parent {
-webkit-box-reflect: left;
}

效果如下:

大致的效果就出来了,当然,0点、3点、6点、9点上左下右 4 个刻度有点问题。不过 -webkit-box-reflect 也提供距离调整功能,再简单修改下代码:

.g-container {
-webkit-box-reflect: below 4px;
}
.g-parent {
-webkit-box-reflect: left -4px;
}

这次,效果就是我们想要的最终效果:

我们就成功地借助 -webkit-box-reflect 节约了 3/4 的标签数量。完整的代码:CodePen Demo -- Clock ticks

-webkit-box-reflect 与剪纸艺术

到这里,我不由得想到,这种对折、再对折,镜像再镜像的方式,与我们小时候的折纸艺术非常的类似

那么,基于这样一个模板:

<div class="g-parent">
<div class="g-container">
<div class="g-item"></div>
</div>
</div>
.g-container {
-webkit-box-reflect: below;
}
.g-parent {
-webkit-box-reflect: left;
}

我只需要绘制 .g-item 里面的内容,将他通过 2 次 -webkit-box-reflect 镜像,就能得到一个剪纸图形

而如何得到随机有意思的不规则图形呢?

clip-path 是个很不错的选择,我们通过 clip-path 随机对一个矩形进行裁剪:

.g-item {
width: 150px;
height: 150px;
background: #000;
clip-path: polygon(25% 0%,71% 66%,59% 0%,79% 23%,95% 4%,100% 40%,77% 100%,38% 100%,47% 71%,36% 30%,23% 60%,0% 100%,5% 37%);
}

效果如下:

经过两次镜像后的效果如下:

是不是有那么点意思了?可以随机利用 clip-path 多尝试几次,可以得到不同的效果:

CodePen Demo -- Pure CSS Page Cutting

-webkit-box-reflect 配合 clip-path 配合 mask

但是上面的图形看着还是太简单了,几个原因,一是对折的次数和角度不够,缺少对折次数和不同角度的对折,二是图形不够负责。

我又想起了之前看到过的一篇类似的剪纸相关的文章 -- Paper Snowflakes: Combining Clipping and Masking in CSS

再上述的基础上,还使用了 mask,将图形切割的更细。

我们再来一次,还是同样的结构,当然,为了得到更负责的图形,我们设置了 4 个 .g-item

<div class="g-parent">
<div class="g-container">
<div class="g-item"></div>
<div class="g-item"></div>
<div class="g-item"></div>
<div class="g-item"></div>
</div>
</div>

首先,还是设置一个 clip-path 切割后的图形:

.g-item:nth-child(1) {
width: 150px;
height: 150px;
background: #000;
clip-path: polygon(17% 41%,6% 39%,16% 91%,18% 78%,56% 11%,28% 71%,99% 67%,25% 65%,69% 72%,46% 28%,90% 76%,67% 34%,48% 30%,79% 36%,59% 15%,23% 92%,16% 1%,32% 81%,72% 38%,50% 59%,71% 98%,66% 87%,83% 14%,36% 71%,49% 7%,9% 25%,52% 76%,10% 83%,17% 41%);
}

效果如下:

这个图可能它会特别的奇怪,没有问题,我们继续。

如果,我们将一个矩形,从左下角开始算,分为 4 份,那么每一个份就是 90° / 4 = 22.5°,我们希望切割得到其中一份:

我们可以借助 mask 去完成这个切割:

.g-item:nth-child(1) {
width: 150px;
height: 150px;
background: #000;
clip-path: polygon(.....);
mask: conic-gradient(from 0turn at 0 100%, #000, #000 22.5deg, transparent 22.5deg, transparent);
}

上述的图形,就被切割成了这样:

OK,基于此,我们可以得到同样的第二份图形,但是我们给它加一个 rotateY(180deg)

.g-item:nth-child(2) {
width: 150px;
height: 150px;
background: #000;
clip-path: polygon(.....);
mask: conic-gradient(from 0turn at 0 100%, #000, #000 22.5deg, transparent 22.5deg, transparent);
transform: rotateY(180deg);
}

效果如下:

我们再通过 rotateZ(),给第二图形旋转一定的角度,让他和第一个贴合在一起:

.g-item:nth-child(2) {
clip-path: polygon(.....);
mask: conic-gradient(from 0turn at 0 100%, #000, #000 22.5deg, transparent 22.5deg, transparent);
transform: rotateY(180deg) rotateZ(-45deg);
}

就得到了一个斜向角度的镜像图像:

因为 .g-item 被切割成了 4 份,所以第 3、4 个图形如法炮制即可,这样,整个 .g-item 的效果如下:

再打开 -webkit-box-reflect,整个的图形效果如下:

这样,一个剪纸图形就诞生啦!

当然,为了得到不一样的效果,我们可以借助 JavaScript 去随机生成 CSS 中的各类参数,完整的代码,大概是这样:

<div class="g-parent">
<div class="g-container">
<div class="g-item"></div>
<div class="g-item"></div>
<div class="g-item"></div>
<div class="g-item"></div>
</div>
</div>
.g-container,
.g-parent {
position: relative;
display: flex;
width: 150px;
height: 150px;
}
.g-item {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background: #000;
transform-origin: 0 100%;
clip-path: var(--polygon, polygon(40% 0%,0% 91%,52% 100%,0% 37%,77% 23%,77% 76%,43% 22%,55% 88%,100% 100%,100% 10%));
}
.g-item {
mask: conic-gradient(from 0turn at 0 100%, #000, #000 22.5deg, transparent 22.5deg, transparent);
}
@for $i from 1 through 5 {
.g-item:nth-child(#{$i}) {
transform: rotateZ(calc(22.5deg * #{$i - 1}));
}
}
.g-item:nth-child(2) {
transform: rotateY(180deg) rotateZ(-60deg);
}
.g-item:nth-child(4) {
transform: rotateY(180deg) rotateZ(-105deg);
}
.g-container {
-webkit-box-reflect: below;
}
.g-parent {
-webkit-box-reflect: left;
}
const ele = document.querySelectorAll('.g-item');

document.addEventListener('click', function(e) {
let num = Math.floor(Math.random() * 30 + 10); const maskR = Math.floor(Math.random() * 22.5 + 22.5 ) + 'deg';
const r1 = Math.floor(Math.random() * 100) + '%';
const r2 = Math.floor(Math.random() * 100) + '%'; let polygon = 'polygon(' + r1 + ' ' + r2 + ','; for (let i=0; i<num; i++) {
const newR1 = Math.floor(Math.random() * 100) + '%';
const newR2 = Math.floor(Math.random() * 100) + '%'; polygon += newR1 + ' ' + newR2 + ','
} polygon += r1 + ' ' + r2 + ')'; [...ele].forEach(item => {
item.setAttribute('style', `--polygon:${polygon};-webkit-mask:conic-gradient(from 0turn at 0 100%, #000, #000 ${maskR}, transparent ${maskR}, transparent)`);
});
});

这样,每次点击鼠标,我们都能得到不同的随机剪纸图案:

看看这个简单录制的 GIF:

完整的代码,你可以猛击这里 CodePen Demo -- Pure CSS Art Page Cutting

最后

本文到此结束,希望对你有帮助

更多精彩 CSS 技术文章汇总在我的 Github -- iCSS ,持续更新,欢迎点个 star 订阅收藏。

如果还有什么疑问或者建议,可以多多交流,原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知。

离谱的 CSS!从表盘刻度到艺术剪纸的更多相关文章

  1. [WPF] 使用 HandyControl 的 CirclePanel 画出表盘刻度

    1. 前言 最近需要一个 WPF 的表盘控件,之前 Cyril-hcj 写过一篇不错的博客 <WPF在圆上画出刻度线>,里面介绍了一些原理及详细实现的代码: double radius = ...

  2. 用 CSS background 实现刻度线的呈现

    有的时候,我们需要在网页中的进度条或某种度量计上呈现一条条的刻度线.例如这种: 简单的实现方式,大致有两种:一是用图片做背景,横向平铺线条图片:二是给每一块刻度区域平铺一个元素,然后用边线实现.身为一 ...

  3. Web 建站技术中,HTML、HTML5、XHTML、CSS、SQL、JavaScript、PHP、ASP.NET、Web Services 是什么(转)

    Web 建站技术中,HTML.HTML5.XHTML.CSS.SQL.JavaScript.PHP.ASP.NET.Web Services 是什么?修改 建站有很多技术,如 HTML.HTML5.X ...

  4. css3-手把手 transform 小时钟

    学习css3ing,正在学习transfomr,突发奇想用此做个小时钟,开始吧: 准备前期工作,把时钟的表盘,时分秒针,实时时间标签 的大概样子做好,效果如图: html代码如下: <div c ...

  5. 用CSS3实现钟表效果

    背景:最近在学习CSS3,看到了一个小案例,通过自己的学习,动手实现了它,现在把它分享出来. 效果图 实现过程 1.首先我们需要在页面中写出一个静态的钟表效果.首先我们需要一个表盘div wrap 对 ...

  6. ACM进阶计划

    ACM进阶计划ACM队不是为了一场比赛而存在的,为的是队员的整体提高.大学期间,ACM队队员必须要学好的课程有:lC/C++两种语言l高等数学l线性代数l数据结构l离散数学l数据库原理l操作系统原理l ...

  7. html中不要忽略一些细节

    1. img必备和可选的参数都有写了上了,但是必备参数里的一个值alt没写(其实一些大型的专业门户网站其实也是有存在一些小问题的,只要我们细心一 点就能发现).虽然这样alt不写,在页面中也不会有任何 ...

  8. 前端技能汇总 Frontend Knowledge Structure

    Frontend Knowledge Structure 项目起源 还记得@jayli 的这幅前端知识结构图么. 图片的形式具有诸多的不便.缺失源图的我们,无法为此图贡献些什么,随着时间的迁移,或许有 ...

  9. ACM进阶

    ACM队不是为了一场比赛而存在的,为的是队员的整体提高. 大学期间,ACM队队员必须要学好的课程有: l C/C++两种语言 l 高等数学 l 线性代数 l 数据结构 l 离散数学 l 数据库原理 l ...

随机推荐

  1. 什么是IOC?

    IoC是什么 Ioc-Inversion of Control,即"控制反转",不是什么技术,而是一种设计思想.在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传 ...

  2. Spring MVC 框架有什么用?

    Spring Web MVC 框架提供 模型-视图-控制器 架构和随时可用的组件,用于开 发灵活且松散耦合的 Web 应用程序.MVC 模式有助于分离应用程序的不同方 面,如输入逻辑,业务逻辑和 UI ...

  3. Leetcode26——删除有序数组中的重复项(双指针法)

    Leetcode26--删除有序数组中的重复项(双指针法) 1. 题目简述 给你一个升序排列的数组 nums ,请你原地 删除重复出现的元素,使每个元素只出现一次 ,返回删除后数组的新长度.元素的相对 ...

  4. 算法 | 串匹配算法之KMP算法及其优化

    主串 s:A B D A B C A B C 子串 t:  A B C A B 问题:在主串 s 中是否存在一段 t 的子串呢? 形如上述问题,就是串匹配类问题.[串匹配--百度百科] 串匹配问题是一 ...

  5. pandas数据读取

    02. Pandas读取数据 本代码演示: pandas读取纯文本文件 读取csv文件 读取txt文件 pandas读取xlsx格式excel文件 pandas读取mysql数据表 1.读取纯文本文件 ...

  6. 制作任意波形发生器MAX038

  7. Vue2.0一个login跳转实例

    需要解决的问题:store存储登录状态Vue-Router导航钩子拦截路由Vue-Resource获取后台的数据需要判断登录返回的user源码参考原文地址 主要技术栈:Vuex + Vue-Resou ...

  8. 原生ES6写的Web游戏:ES6-Mario,小美女,小帅哥快来玩啊~~

    ? ES6-Mario 这是一个用原生ES6语法和HTML5新特性写成的Web 游戏. 通过这个项目,你可以在实践中对ES6的主要内容.HTML Canvas 相关API以及Webpack的基础配置有 ...

  9. PAT B1076 Wifi密码

    题目描述: 下面是微博上流传的一张照片:"各位亲爱的同学们,鉴于大家有时需要使用 wifi,又怕耽误亲们的学习,现将 wifi 密码设置为下列数学题答案:A-1:B-2:C-3:D-4:请同 ...

  10. 子线程中如何修改ui界面

    1.Android进程 一个应用程序被启动时,系统默认创建执行一个叫做"main"的线程.这个线程也是你的应用与界面工具包(android.widget和android.view包 ...