作者: 晴栀
珍惜拥有的每一天,拥抱生命里的每一个瞬间。莫到日历沙沙划去了岁月,莫到钟摆悠悠摇老了容颜。不要一眨眼,芳华岁月都已成过往之事。才发现,时光匆匆逝去光阴真如箭。
文章目录
有趣的css动画
使用 border-image 实现渐变边框
border-image
是 CSS 规范 CSS Backgrounds and Borders Module Level 3 (最新一版的关于 background 和 border 的官方规范) 新增的一个属性值。
https://drafts.csswg.org/css-backgrounds-3/#border-images
顾名思义,我们可以给 border 元素添加 image,类似于
background-image
,可以是图片也可以是渐变,不再局限于纯色。
使用 border-image 实现渐变边框
有了
border-image
之后,实现渐变边框变得很方便
不过多介绍 border-image 的语法,读者需要自行了解一下。
https://developer.mozilla.org/zh-CN/docs/Web/CSS/border-image
border-image
CSS属性允许在元素的边框上绘制图像。这使得绘制复杂的外观组件更加简单,也不用在某些情况下使用九宫格了。使用border-image
时,其将会替换掉border-style
属性所设置的边框样式。虽然规范要求使用border-image
时边框样式必须存在,但一些浏览器可能没有实现这一点。
特别注意,若
border-image-source
(此值可用border-image-source或border-image简写设置) 的值为none
或者图片不能显示,则将应用border-style。
实现代码如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
html,
body {
height: 100%;
width: 100%;
}
body {
display: flex;
justify-content: center;
align-items: center;
}
.box {
width: 200px;
height: 100px;
border-radius: 10px;
border: 30px solid;
border-image-source: linear-gradient(45deg, gold, deeppink);
border-image-slice: 10;
border-image-repeat: stretch;
}
</style>
</head>
<body>
<div class="box"></div>
</body>
</html>
border-radius 失效
仔细看上面的 Demo,设置了
border-radius: 10px
但是实际表现没有圆角。使用border-image
最大的问题在于,设置的border-radius
会失效。
我们无法得到一个带圆角的渐变边框。原因,查看官方规范 W3C 解释如下:
A box’s backgrounds, but not its border-image, are clipped to the appropriate curve (as determined by ‘background-clip’). Other effects that clip to the border or padding edge (such as ‘overflow’ other than ‘visible’) also must clip to the curve. The content of replaced elements is always trimmed to the content edge curve. Also, the area outside the curve of the border edge does not accept mouse events on behalf of the element.
为此,我们得另辟蹊径或者稍加改进,得到带圆角的渐变边框。
代码如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body,
html {
width: 100%;
min-height: 100%;
display: flex;
background: #fee;
}
.pesudo {
position: relative;
width: 200px;
height: 100px;
margin: auto;
}
.pesudo::after {
content: "";
position: absolute;
width: 180px;
height: 80px;
background: #fff;
top: 50%;
left: 50%;
transform: translate(50px, -50%);
z-index: 3;
animation: move 4s infinite;
}
.pesudo::before {
content: "";
position: absolute;
width: 200px;
height: 100px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 10px;
background: linear-gradient(45deg, gold, deeppink);
z-index: 2;
}
@keyframes move {
0% {
transform: translate(50px, -50%);
}
50%,
100% {
transform: translate(-50%, -50%);
}
}
</style>
</head>
<body>
<!--伪元素遮罩-->
<div class="pesudo">
</div>
</body>
</html>
缺点
- 这个方案有两个问题,
第一个是多使用了两个元素(当然在这里是( ::before 和 ::after)
,其次最致命的是,如果要求边框內的背景是透明的,此方案便行不通了。- 并且框内不能添加元素
background-image + 伪元素
第一种方法,我们不再使用
border-image
,而是使用background-image
+ 伪元素 的方案,这也是在border-image
规范没有出现最常用的方法。
使用 background-clip 实现
使用
background-clip: content-box
以及background-clip: border-box
配合使用。background-clip:background-clip 设置元素的背景(背景图片或颜色)是否延伸到边框下面。它的部分取值和
box-sizing
类似。其中,
background-clip: border-box
表示设置的背景background-image
将延伸至边框background-clip: content-box
表示设置的背景background-image
被裁剪至内容区(content box)外沿
这里,我们使用设置两个
background-image
,设置两个background-clip
,并且将border
设置为透明即可:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
width: 200px;
height: 100px;
border: solid 10px transparent;
border-radius: 10px;
background-image: linear-gradient(#fee, #fee), linear-gradient(to right, green, gold);
background-origin: border-box;
background-clip: content-box, border-box;
}
</style>
</head>
<body>
<div></div>
</body>
</html>
因为用到了
background-clip: border-box
,所以还需要background-origin: border-box
使图案完整显示,可以尝试下关掉这个属性,即可明白为什么需要这样做。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
width: 200px;
height: 100px;
border: solid 10px transparent;
border-radius: 10px;
background-image: linear-gradient(#fee, #fee), linear-gradient(to right, green, gold);
/* background-origin: border-box; */
background-clip: content-box, border-box;
}
</style>
</head>
<body>
<div></div>
</body>
</html>
background-origin
border-box
背景图片的摆放以border区域为参考
padding-box
背景图片的摆放以padding区域为参考
content-box
背景图片的摆放以content区域为参考
background-origin
规定了指定背景图片background-image
属性的原点位置的背景相对区域.注意:当使用 background-attachment 为fixed时,该属性将被忽略不起作用。
https://developer.mozilla.org/zh-CN/docs/Web/CSS/background-attachment
fixed
此关键属性值表示背景相对于视口固定。即使一个元素拥有滚动机制,背景也不会随着元素的内容滚动。local
此关键属性值表示背景相对于元素的内容固定。如果一个元素拥有滚动机制,背景将会随着元素的内容滚动, 并且背景的绘制区域和定位区域是相对于可滚动的区域而不是包含他们的边框。scroll
此关键属性值表示背景相对于元素本身固定, 而不是随着它的内容滚动(对元素边框是有效的)。- 相关我文章
https://www.cnblogs.com/shytong/p/5077129.html
- 假如
background简写
中没有设置该值,那么在background
简写值后指定background-origin
,那么后面的值就会覆盖简写值
,其实说白了,就是后出现的值会覆盖前面的值。
缺点
与第一种方法类似,如果要求边框內的背景是透明的,此方案便行不通了。
border-image + overflow: hidden
这个方法也很好理解,既然设置了
background-image
的元素的border-radius
失效。那么,我们只需要给它加一个父容器,父容器设置overflow: hidden
+border-radius
即可:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body,
html {
width: 100%;
min-height: 100%;
display: flex;
background: #fee;
justify-content: center;
align-items: center;
}
.border-image-overflow {
position: relative;
width: 220px;
height: 120px;
border-radius: 10px;
overflow: hidden;
}
.content {
position: absolute;
width: 200px;
height: 100px;
border: 10px solid;
border-radius: 10px;
border-image: linear-gradient(45deg, gold, deeppink) 1;
}
</style>
</head>
<body>
<div class="border-image-overflow">
<div class="content">
</div>
</div>
</body>
</html>
border-image + clip-path
设置了
background-image
的元素的border-radius
失效。但是在 CSS 中,还有其它方法可以产生带圆角的容器,那就是借助clip-path
。
clip-path,一个非常有意思的 CSS 属性。
https://developer.mozilla.org/zh-CN/docs/Web/CSS/clip-path
clip-path
CSS 属性可以创建一个只有元素的部分区域可以显示的剪切区域。区域内的部分显示,区域外的隐藏。剪切区域是被引用内嵌的URL定义的路径或者外部 SVG 的路径。
<clip-source>
- 用
<url>
表示剪切元素的路径
<basic-shape>
- 一种形状,其大小和位置由<几何盒>值定义。如果没有指定几何框,则边框将用作参考框
<geometry-box>
- 如果同
<basic-shape>
一起声明,它将为基本形状提供相应的参考框盒。通过自定义,它将利用确定的盒子边缘包括任何形状边角(比如说,被border-radius
定义的剪切路径)。几何框盒可以有以下的值中的一个:
margin-box
使用 margin box 作为引用框。border-box
使用 border box 作为引用框。padding-box
使用 padding box 作为引用框。content-box
使用 content box 作为引用框。fill-box
利用对象边界框作为引用框。stroke-box
使用笔触边界框(stroke bounding box)作为引用框view-box
使用最近的 SVG 视口(viewport)作为引用框。如果viewBox
属性被指定来为元素创建 SVG 视口,引用框将会被定位在坐标系的原点,引用框位于由viewBox
属性建立的坐标系的原点,引用框的尺寸用来设置viewBox
属性的宽高值。- none
不创建的剪切路径。
<clip-source> | [ <basic-shape> || <geometry-box> ] | none
where
<clip-source> = <url>
<basic-shape> = <inset()> | <circle()> | <ellipse()> | <polygon()> | <path()>
<geometry-box> = <shape-box> | fill-box | stroke-box | view-box
where
<inset()> = inset( <length-percentage>{1,4} [ round <'border-radius'> ]? )
<circle()> = circle( [ <shape-radius> ]? [ at <position> ]? )
<ellipse()> = ellipse( [ <shape-radius>{2} ]? [ at <position> ]? )
<polygon()> = polygon( <fill-rule>? , [ <length-percentage> <length-percentage> ]# )
<path()> = path( [ <fill-rule>, ]? <string> )
<shape-box> = <box> | margin-box
where
<length-percentage> = <length> | <percentage>
<shape-radius> = <length-percentage> | closest-side | farthest-side
<position> = [ [ left | center | right ] || [ top | center | bottom ] | [ left | center | right | <length-percentage> ] [ top | center | bottom | <length-percentage> ]? | [ [ left | right ] <length-percentage> ] && [ [ top | bottom ] <length-percentage> ] ]
<fill-rule> = nonzero | evenodd
<box> = border-box | padding-box | content-box
语法
/* Keyword values */
clip-path: none;
/* <clip-source> values */
clip-path: url(resources.svg#c1);
/* <geometry-box> values */
clip-path: margin-box;
clip-path: border-box;
clip-path: padding-box;
clip-path: content-box;
clip-path: fill-box;
clip-path: stroke-box;
clip-path: view-box;
/* <basic-shape> values */
clip-path: inset(100px 50px);
clip-path: circle(50px at 0 100px);
clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
clip-path: path('M0.5,1 C0.5,1,0,0.7,0,0.3 A0.25,0.25,1,1,1,0.5,0.3 A0.25,0.25,1,1,1,1,0.3 C1,0.7,0.5,1,0.5,1 Z');
/* Box and shape values combined */
clip-path: padding-box circle(50px at 0 100px);
/* Global values */
clip-path: inherit;
clip-path: initial;
clip-path: unset;
案例
.border-image-clip-path {
position: relative;
width: 200px;
background-color: aqua;
height: 200px;
border: 50px solid;
}
<body>
<div class="border-image-clip-path"></div>
</body>
clip-path: circle(40%);
clip-path: ellipse(130px 140px at 10% 20%);
clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
实现代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body,
html {
width: 100%;
min-height: 100%;
display: flex;
background: #fee;
justify-content: center;
align-items: center;
}
.border-image-clip-path {
position: relative;
width: 200px;
background-color: aqua;
height: 100px;
border: 10px solid;
border-image: linear-gradient(45deg, gold, deeppink) 1;
clip-path: inset(0 round 10px);
}
</style>
</head>
<body>
<div class="border-image-clip-path"></div>
</body>
</html>
border-image 使用渐变
我们可以利用
border-image + filter + clip-path
实现渐变变换的圆角边框:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body,
html {
width: 100%;
min-height: 100%;
display: flex;
}
.border-image-clip-path {
width: 200px;
height: 100px;
margin: auto;
border: 10px solid;
border-image: linear-gradient(45deg, gold, deeppink) 1;
clip-path: inset(0px round 10px);
animation: huerotate 6s infinite linear;
filter: hue-rotate(360deg);
}
@keyframes huerotate {
0% {
filter: hue-rotate(0deg);
}
100% {
filter: hue-rorate(360deg);
}
}
</style>
</head>
<body>
<div class="border-image-clip-path"></div>
</body>
</html>
https://codepen.io/Chokcoco/pen/povBORP
边框hover动画
长度变化动画
先来个比较简单的,实现一个类似这样的边框效果:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
:root {
--borderColor: #03A9F3;
}
html,
body {
height: 100%;
width: 100%;
}
body {
display: flex;
justify-content: center;
align-items: center;
background-image: linear-gradient(to top, #fdcbf1 0%, #fdcbf1 1%, #e6dee9 100%);
}
.box {
position: relative;
width: 140px;
height: 64px;
margin: auto;
border: 1px solid #03A9F3;
cursor: pointer;
/*文字样式 */
text-align: center;
line-height: 64px;
color: #ffffff;
font-size: 26px;
font-weight: bold;
}
.box::before,
.box::after {
content: "";
position: absolute;
width: 20px;
height: 20px;
transition: .3s ease-in-out;
}
.box::before {
top: -5px;
left: -5px;
border-top: 1px solid var(--borderColor);
border-left: 1px solid var(--borderColor);
}
.box::after {
right: -5px;
bottom: -5px;
border-bottom: 1px solid var(--borderColor);
border-right: 1px solid var(--borderColor);
}
.box:hover::before,
.box:hover::after {
width: calc(100% + 9px);
height: calc(100% + 9px);
}
</style>
</head>
<body>
<div class="box">
hover
</div>
</body>
</html>
接下来,会开始加深一些难度。
虚线边框动画
首先使用
dashed
关键字,可以方便的创建虚线边框
。
div {
border: 1px dashed #333;
}
当然,
我们的目的是让边框能够动起来
。使用 dashed 关键字是没有办法的。但是实现虚线的方式在 CSS 中有很多种,譬如渐变就是一种很好的方式:
div {
background: linear-gradient(90deg, #333 50%, transparent 0) repeat-x;
background-size: 4px 1px;
background-position: 0 0;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
html,
body {
height: 100%;
width: 100%;
}
body {
display: flex;
justify-content: center;
align-items: center;
background-image: linear-gradient(to top, #fdcbf1 0%, #fdcbf1 1%, #e6dee9 100%);
}
div {
width: 100px;
height: 100px;
background: linear-gradient(90deg, #333 50%, transparent 0) repeat-x;
background-size: 4px 1px;
background-position: 0 0;
}
</style>
</head>
<body>
<div></div>
</body>
</html>
好了,渐变支持多重渐变,我们把容器的·
4 个边都用渐变
表示即可:
div {
width: 100px;
height: 100px;
background: linear-gradient(90deg, #333 50%, transparent 0) repeat-x, linear-gradient(90deg, #333 50%, transparent 0) repeat-x, linear-gradient(0deg, #333 50%, transparent 0) repeat-y, linear-gradient(0deg, #333 50%, transparent 0) repeat-y;
background-size: 4px 1px, 4px 1px, 1px 4px, 1px 4px;
background-position: 0 0, 0 100%, 0 0, 100% 0;
}
效果图
OK,至此,我们的虚线边框动画其实算是完成了一大半了。虽然border-style: dashed
不支持动画,但是渐变支持呀。我们给上述 div 再加上一个hover
效果,hover
的时候新增一个animation
动画,改变元素的background-position
即可。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
html,
body {
height: 100%;
width: 100%;
}
body {
display: flex;
justify-content: center;
align-items: center;
background-image: linear-gradient(to top, #fdcbf1 0%, #fdcbf1 1%, #e6dee9 100%);
}
div {
width: 100px;
height: 100px;
background: linear-gradient(90deg, #333 50%, transparent 0) repeat-x, linear-gradient(90deg, #333 50%, transparent 0) repeat-x, linear-gradient(0deg, #333 50%, transparent 0) repeat-y, linear-gradient(0deg, #333 50%, transparent 0) repeat-y;
background-size: 4px 1px, 4px 1px, 1px 4px, 1px 4px;
background-position: 0 0, 0 100%, 0 0, 100% 0;
}
div:hover {
animation: linearGradientMove .3s infinite linear;
}
@keyframes linearGradientMove {
100% {
background-position: 4px 0, -4px 100%, 0 -4px, 100% 4px;
}
}
</style>
</head>
<body>
<div></div>
</body>
</html>
效果图
这里还有另外一个小技巧,如果我们希望虚线边框动画是从其他边框,过渡到虚线边框,再行进动画。完全由渐变来模拟也是可以的,如果想节省一些代码,使用
border
会更快一些,譬如这样:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
html,
body {
height: 100%;
width: 100%;
}
body {
display: flex;
justify-content: center;
align-items: center;
background-image: linear-gradient(to top, #fdcbf1 0%, #fdcbf1 1%, #e6dee9 100%);
}
div {
border: 1px solid #333;
width: 100px;
height: 100px;
}
div:hover {
border: none;
background: linear-gradient(90deg, #333 50%, transparent 0) repeat-x, linear-gradient(90deg, #333 50%, transparent 0) repeat-x, linear-gradient(0deg, #333 50%, transparent 0) repeat-y, linear-gradient(0deg, #333 50%, transparent 0) repeat-y;
background-size: 4px 1px, 4px 1px, 1px 4px, 1px 4px;
background-position: 0 0, 0 100%, 0 0, 100% 0;
animation: linearGradientMove .3s infinite linear;
}
@keyframes linearGradientMove {
100% {
background-position: 4px 0, -4px 100%, 0 -4px, 100% 4px;
}
}
</style>
</head>
<body>
<div></div>
</body>
</html>
由于
border
和background
在盒子模型上位置的差异,视觉上会有一个很明显的错位的感觉:
要想解决这个问题,我们可以把border
替换成outline
,因为outline
可以设置outline-offset
。便能完美解决这个问题:
div {
outline: 1px solid #333;
outline-offset: -1px;
}
div:hover {
outline: none;
}
outline
CSS 的
outline
属性是在一条声明中设置多个轮廓属性的简写属性 , 例如outline-style
,outline-width
和outline-color。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body,
html {
width: 100%;
min-height: 100%;
display: flex;
}
.outline {
width: 200px;
height: 100px;
margin: auto;
/* 字体 */
display: flex;
text-align: center;
align-items: center;
background: linear-gradient(45deg, gold, deeppink);
outline: solid;
}
</style>
</head>
<body>
<div class="outline">
This is a box with an outline around it.
</div>
</body>
</html>
outline: dashed red;
outline: 1rem solid;
outline: thick double #32a1ce;
outline: 8px ridge rgba(170, 50, 220, .6);
border-radius: 2rem;
border 和 outline
border 和 outline
很类似,但有如下区别:
outline
不占据空间,绘制于元素内容周围。
根据规范,outline通常是矩形,但也可以是非矩形的。
语法
/* 样式 */
outline: solid;
/* 颜色 | 样式 */
outline: #f66 dashed;
/* 样式 | 宽度 */
outline: inset thick;
/* 颜色 | 样式 | 宽度 */
outline: green solid 3px;
/* 全局值 */
outline: inherit;
outline: initial;
outline: unset;
最终效果
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
html,
body {
height: 100%;
width: 100%;
}
body {
display: flex;
justify-content: center;
align-items: center;
background-image: linear-gradient(120deg, #89f7fe 0%, #66a6ff 100%);
}
div {
width: 200px;
height: 50px;
outline: 1px solid rgb(255, 255, 255);
outline-offset: -1px;
text-align: center;
line-height: 50px;
color: white;
}
div:hover {
outline: none;
box-shadow: inset #ffffff44 0px 0px 6px 5px;
background: linear-gradient(90deg, rgb(255, 255, 255) 50%, transparent 0) repeat-x, linear-gradient(90deg, rgb(255, 254, 254) 50%, transparent 0) repeat-x, linear-gradient(0deg, rgb(248, 248, 248) 50%, transparent 0) repeat-y, linear-gradient(0deg, rgb(247, 244, 244) 50%, transparent 0) repeat-y;
background-size: 4px 1px, 4px 1px, 1px 4px, 1px 4px;
background-position: 0 0, 0 100%, 0 0, 100% 0;
animation: linearGradientMove .3s infinite linear;
}
@keyframes linearGradientMove {
100% {
background-position: 4px 0, -4px 100%, 0 -4px, 100% 4px;
}
}
</style>
</head>
<body>
<div>
Hover Me
</div>
</body>
</html>
其实由于背景和边框的特殊关系,使用 border 的时候,通过修改
background-position
也是可以解决的。
巧妙使用渐变
用渐变,不仅仅只是能完成上述的效果。
条纹背景
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
position: relative;
}
div::after {
content: '';
position: absolute;
left: -50%;
top: -50%;
width: 200%;
height: 200%;
background-repeat: no-repeat;
background-size: 50% 50%, 50% 50%;
background-position: 0 0, 100% 0, 100% 100%, 0 100%;
background-image: linear-gradient(#399953, #399953), linear-gradient(#fbb300, #fbb300), linear-gradient(#d53e33, #d53e33), linear-gradient(#377af5, #377af5);
}
</style>
</head>
<body>
<div>
</div>
</body>
</html>
旋转
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
html,
body {
height: 100%;
width: 100%;
}
body {
display: flex;
justify-content: center;
align-items: center;
background-image: linear-gradient(to top, #fdcbf1 0%, #fdcbf1 1%, #e6dee9 100%);
}
.box {
width: 100px;
height: 100px;
position: relative;
border-radius: 20px;
overflow: hidden;
}
.box::after {
content: '';
position: absolute;
left: -50%;
top: -50%;
width: 200%;
height: 200%;
background-repeat: no-repeat;
background-size: 50% 50%, 50% 50%;
background-position: 0 0, 100% 0, 100% 100%, 0 100%;
background-image: linear-gradient(#399953, #399953), linear-gradient(#fbb300, #fbb300), linear-gradient(#d53e33, #d53e33), linear-gradient(#377af5, #377af5);
animation: rotate 4s linear infinite;
}
@keyframes rotate {
100% {
transform: rotate(1turn);
}
}
</style>
</head>
<body>
<div class="box">
</div>
</body>
</html>
最后,再利用一个伪元素,将中间遮罩起来,一个 Nice 的边框动画就出来了 (动画会出现半透明元素,方便示意看懂原理):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@keyframes rotate {
100% {
transform: rotate(1turn);
}
}
.rainbow {
position: relative;
z-index: 0;
width: 400px;
height: 300px;
border-radius: 10px;
overflow: hidden;
padding: 2rem;
}
.rainbow::before {
content: '';
position: absolute;
z-index: -2;
left: -50%;
top: -50%;
width: 200%;
height: 200%;
background-color: #399953;
background-repeat: no-repeat;
background-size: 50% 50%, 50% 50%;
background-position: 0 0, 100% 0, 100% 100%, 0 100%;
background-image: linear-gradient(#399953, #399953), linear-gradient(#fbb300, #fbb300), linear-gradient(#d53e33, #d53e33), linear-gradient(#377af5, #377af5);
animation: rotate 4s linear infinite;
}
.rainbow::after {
content: '';
position: absolute;
z-index: -1;
left: 6px;
top: 6px;
width: calc(100% - 12px);
height: calc(100% - 12px);
background: white;
border-radius: 5px;
animation: opacityChange 3s infinite alternate;
}
@keyframes opacityChange {
50% {
opacity: 1;
}
100% {
opacity: .5;
}
}
</style>
</head>
<body>
<div class="rainbow"></div>
</body>
</html>
改变渐变的颜色
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@keyframes rotate {
100% {
transform: rotate(1turn);
}
}
.rainbow {
position: relative;
z-index: 0;
width: 400px;
height: 300px;
border-radius: 10px;
overflow: hidden;
padding: 2rem;
}
.rainbow::before {
content: '';
position: absolute;
left: -50%;
top: -50%;
width: 200%;
height: 200%;
z-index: -2;
background-color: #fff;
background-repeat: no-repeat;
background-size: 50% 50%;
background-position: 0 0;
background-image: linear-gradient(#399953, #399953);
animation: rotate 4s linear infinite;
}
.rainbow::after {
content: '';
position: absolute;
z-index: -1;
left: 6px;
top: 6px;
width: calc(100% - 12px);
height: calc(100% - 12px);
background: white;
border-radius: 5px;
animation: opacityChange 3s infinite alternate;
}
@keyframes opacityChange {
50% {
opacity: 1;
}
100% {
opacity: .5;
}
}
</style>
</head>
<body>
<div class="rainbow"></div>
</body>
</html>
Wow,很不错的样子。不过如果是单线条,有个很明显的缺陷,就是边框的末尾是一个小三角而不是垂直的,可能有些场景不适用或者 PM 接受不了。
那有没有什么办法能够消除掉这些小三角呢?有的,在下文中我们再介绍一种方法,利用clip-path
,消除掉这些小三角。
conic-gradient`的妙用
再介绍
clip-path
之前,先讲讲角向渐变。
上述主要用到了的是线性渐变linear-gradient
。我们使用角向渐变conic-gradient
其实完全也可以实现一模一样的效果。
conic-gradient:
圆锥形渐变,它的两个兄弟line-gradient(线性渐变)、radial-gradient(径向渐变),
算是最早认识的渐变属性。
圆锥渐变的起始点是图形中心,渐变方向以顺时针方向绕中心旋转实现渐变效果。
- 兼容性
根据 can i use,目前只在chrome 69及以上支持。
可以使用polyfill垫片库,解决兼容性问题。垫片库会根据css语法,生成对应的圆锥渐变图案,并且转化为 base64 代码。
颜色表盘
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
/* 起始处与结尾处衔接不够自然 */
.color1 {
width: 200px;
height: 200px;
border-radius: 50%;
background: conic-gradient(red, orange, yellow, green, teal, blue, purple)
}
/* 起始处与结尾处衔接不够自然,解决:在结尾加入开始的颜色 */
.color2 {
width: 200px;
height: 200px;
border-radius: 50%;
background: conic-gradient(red, orange, yellow, green, teal, blue, purple, red);
}
</style>
</head>
<body>
<section class="color1"></section>
<section class="color2"></section>
</body>
</html>
饼图
<section class="pie"></section>
.pie {
width: 200px;
height: 200px;
border-radius: 50%;
/* 百分比 写法一 */
background: conic-gradient(pink 0, pink 30%, yellow 30%, yellow 70%, lime 70%, lime 100%);
/* 写法二 不支持 chrome69 */
/* background: conic-gradient(pink 0 30%, yellow 0 70%, lime 0 100%); */
}
菱形背景
<section class="rhombus"></section>
.rhombus {
width: 200px;
height: 200px;
background: conic-gradient(#000 0, #000 12.5%, #fff 12.5%, #fff 37.5%, #000 37.5%, #000 62.5%, #fff 62.5%, #fff 87.5%, #000 87.5%,#000 0);
border: 1px solid #000;
background-size: 50px 50px;
}
仪表盘
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.bg {
position: relative;
margin: 50px auto;
width: 400px;
height: 400px;
border-radius: 50%;
background: conic-gradient(#f1462c 0%, #fc5d2c 12.4%, #fff 12.5%, #fff 12.5%, #fc5d2c 12.5%, #fba73e 24.9%, #fff 24.9%, #fff 25%, #fba73e 25%, #e0fa4e 37.4%, #fff 37.4%, #fff 37.5%, #e0fa4e 37.5%, #12dd7e 49.9%, #fff 49.9%, #fff 50%, #12dd7e 50%, #0a6e3f 62.4%, #fff 62.4%, #fff 62.5%);
transform: rotate(-112.5deg);
transform-origin: 50% 50%;
}
.bg::before {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 370px;
height: 370px;
border-radius: 50%;
background: #fff;
}
.bg::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 320px;
height: 320px;
border-radius: 50%;
background: radial-gradient(#fff 0%, #fff 25%, transparent 25%, transparent 100%), conic-gradient(#f1462c 0, #f1462c 12.5%, #fba73e 12.5%, #fba73e 25%, #e0fa4e 25%, #e0fa4e 37.5%, #12dd7e 37.5%, #12dd7e 50%, #0a6e3f 50%, #0a6e3f 62.5%, #fff 62.5%, #fff 100%);
}
.point {
position: absolute;
width: 30px;
height: 30px;
transform: translate(-50%, -50%);
left: 50%;
top: 50%;
background: radial-gradient(#467dc6 0%, #a4c6f3 100%);
border-radius: 50%;
z-index: 999;
}
.point::before {
content: "";
position: absolute;
width: 5px;
height: 350px;
left: 50%;
top: 50%;
transform: translate(-50%, -50%) rotate(0);
border-radius: 100% 100% 5% 5%;
background: linear-gradient( 180deg, #9bc7f6 0, #467dc6 50%, transparent 50%, transparent 100%);
animation: pointerRotate 3s cubic-bezier(.93, 1.32, .89, 1.15) infinite;
}
@keyframes pointerRotate {
50% {
transform: translate(-50%, -50%) rotate(150deg);
}
100% {
transform: translate(-50%, -50%) rotate(150deg);
}
}
</style>
</head>
<body>
<div class="bg">
<div class="point"></div>
</div>
</body>
</html>
我们试着使用
conic-gradient
也实现一次,这次换一种暗黑风格。核心代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: rgb(8, 8, 8);
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@keyframes rotate {
100% {
transform: rotate(1turn);
}
}
.rainbow {
position: relative;
z-index: 0;
width: 400px;
height: 300px;
border-radius: 10px;
overflow: hidden;
padding: 2rem;
}
.rainbow::before {
content: '';
position: absolute;
left: -50%;
top: -50%;
width: 200%;
height: 200%;
z-index: -2;
background-repeat: no-repeat;
background-size: 50% 50%;
background-position: 0 0;
background: conic-gradient(transparent, rgba(168, 239, 255, 1), transparent 30%);
animation: rotate 4s linear infinite;
}
.rainbow::after {
content: '';
position: absolute;
z-index: -1;
left: 6px;
top: 6px;
width: calc(100% - 12px);
height: calc(100% - 12px);
background-color: rgb(8, 8, 8);
border-radius: 5px;
/* animation: opacityChange 3s infinite alternate; */
}
@keyframes opacityChange {
50% {
opacity: 1;
}
100% {
opacity: .5;
}
}
</style>
</head>
<body>
<div class="rainbow"></div>
</body>
</html>
clip-path
的妙用
- 又是老朋友
clip-path
,有意思的事情它总不会缺席。clip-path
本身是可以进行坐标点的动画的,从一个裁剪形状变换到另外一个裁剪形状。- 利用这个特点,我们可以巧妙的实现这样一种 border 跟随效果。伪代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body,
html {
width: 100%;
height: 100%;
display: flex;
background-color: red;
}
div {
position: relative;
margin: auto;
width: 160px;
line-height: 160px;
text-align: center;
font-size: 24px;
}
div::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 2px solid gold;
transition: all .5s;
animation: clippath 3s infinite linear;
}
@keyframes clippath {
0%,
100% {
clip-path: inset(0 0 95% 0);
}
25% {
clip-path: inset(0 95% 0 0);
}
50% {
clip-path: inset(95% 0 0 0);
}
75% {
clip-path: inset(0 0 0 95%);
}
}
.bg::before {
background: rgba(255, 215, 0, .5);
}
</style>
</head>
<body>
<div>Hello</div>
<div class="bg">示意图</div>
</body>
</html>
这里,因为会裁剪元素,借用伪元素作为背景进行裁剪并动画即可,使用
clip-path
的优点了,切割出来的边框不会产生小三角。同时,这种方法,也是支持圆角border-radius
的。
如果我们把另外一个伪元素也用上,实际实现一个按钮样式,可以得到这样的效果:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body,
html {
width: 100%;
height: 100%;
display: flex;
}
.btn {
position: relative;
margin: auto;
width: 120px;
line-height: 64px;
text-align: center;
color: #fff;
font-size: 20px;
border: 2px solid gold;
border-radius: 10px;
background: gold;
transition: all .3s;
cursor: pointer;
}
.btn:hover {
filter: contrast(1.1);
}
.btn:active {
filter: contrast(0.9);
}
.btn::before,
.btn::after {
content: "";
position: absolute;
top: -10px;
left: -10px;
right: -10px;
bottom: -10px;
border: 2px solid gold;
transition: all .5s;
animation: clippath 3s infinite linear;
border-radius: 10px;
}
.btn::after {
animation: clippath 3s infinite -1.5s linear;
}
@keyframes clippath {
0%,
100% {
clip-path: inset(0 0 98% 0);
}
25% {
clip-path: inset(0 98% 0 0);
}
50% {
clip-path: inset(98% 0 0 0);
}
75% {
clip-path: inset(0 0 0 98%);
}
}
</style>
</head>
<body>
<div class="btn">Hover</div>
</body>
</html>
overflow的妙用
下面这个技巧利用 overflow 实现。实现这样一个边框动画:
为何说是利用overflow
实现?
- 示意图
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body,
html {
width: 100%;
height: 100%;
display: flex;
background: yellowgreen;
}
div {
position: relative;
margin: auto;
width: 200px;
height: 100px;
line-height: 100px;
text-align: center;
overflow: hidden;
}
div::after {
content: "Hover Me";
position: absolute;
top: 4px;
bottom: 4px;
right: 4px;
left: 4px;
line-height: 92px;
font-size: 24px;
background: #fff;
border: 2px solid yellowgreen;
cursor: pointer;
color: yellowgreen;
}
div::before {
content: "";
position: absolute;
top: 0px;
bottom: 0px;
right: -20px;
left: 0px;
background: #fff;
transform: rotateZ(-90deg) translate(-100%, -100%);
transform-origin: top left;
transition: transform .3s;
transition-timing-function: linear;
}
div:hover {
filter: contrast(1.2);
}
div:hover::before {
transform: rotateZ(0deg) translate(0%, -0%);
}
div:nth-child(2) {
overflow: unset;
}
div::after {
content: "";
}
div:nth-child(1):hover~div:nth-child(2)::before {
transform: rotateZ(0deg) translate(0%, -0%);
}
</style>
</head>
<body>
<div>Hover Me</div>
<div>Hover Me</div>
</body>
</html>
两个核心点:
- 我们利用
overflow: hidden
,把原本在容器外的一整个元素隐藏了起来- 利用了
transform-origin
,控制了元素的旋转中心
发现没,其实几乎大部分的有意思的 CSS 效果,都是运用了类似技巧
总结
简单的说就是,我们看到的动画只是原本现象的一小部分,通过特定的裁剪、透明度的变化、遮罩等,让我们最后只看到了原本现象的一部分。