这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战

很多Java小伙伴自嘲是Spring工程师,但更可悲的是,还有很多前端同学压根就意识不到自己是Vue工程师

嗯,这说的就是我啊!

为什么学mybatis

最近小伙伴推了一个好用的前后台分离的管理系统:若依(Ruoyi),相信不少小伙伴都用过或者听过。

基础页面长这个样子:

image.png

之前生产环境用的是国产开源框架jfinal中集成的数据库访问组件,为了避免把自己绑死的一个框架上,多学点东西总是有益的。

若依中用的数据库组件是mybatis,也借着更文,系统的学习一下mybatis吧。

mybatis官方文档

目前是按照这一套官网文档学习。

若依中的mybatis架构

按照程序员的直觉和若依框架中的简单教程做了一个简单页面,执行了几个简单的sql,大致清楚了mybatis的框架。若依的框架大致有这样几部分:

controller

controller基本位于若依框架中的admin中:

image.png

service

service则根据功能版块拆分到各个子module中,如common, framework, system等,需要自己的业务版块可以新建新的modeul。 (这一点体现出jetbrain idea是真的好用)

mybatis架构

mybatis主要体现在若依的system版块中。System版块的项目截图如下图:

image.png

可以清晰的看到,其中分为domainmapperservice文件夹,以及保存sql的xml文件包。

domain

domain,也就是通常说的DO, Domain Object,领域对象,网上查到的概念是:从现实世界中抽象出来的有形或无形的业务实体。

mapper

mybatis基础的通用mapper,通过配置可以自动生成单表的增删改查。这个是用来降低开发成本,减少程序员的工作量的。

service

这各基本和mybatis没什么关系了,是向上提供服务方法的,一般应该是一个完整的业务方法。

mybatis的配置

若依哪里配置了mybatis呢?

根据以往其他框架的经验,我们从spring的配置文件入手即可在admin版块中发现:

image.png

这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战

问题起源

生产环境搭配nginx配置多台机器后,总担心出问题,例如定时任务重复执行的问题等等。最近有个需求,为避免透漏公司机密(嗯,就是懒而已),抽离业务,概况实现,大致为:

在接口拦截器中添加一段代码,记录用户的最近一次的操作时间!

目前生产环境多台机器,跑着同样的业务,首先想到的是这样的代码:

1
2
3
4
if 数据库中没有该用户记录
INSERT INTO table_name ....
else
UPDATE table_name SET .....

如果按照以上代码执行,也不是不可以,但总担心因为异步同步问题导致数据库中会出现一个人两条甚至多条记录。例如一台机器上的一个接口因为机器问题/网络问题,导致延迟或者卡顿,从而可能出现:a, b两台机器,a,b同时执行到if判断语句,从而导致数据库中会插入两条数据。这个详细的逻辑相信大家都看得懂。

那么,一个sql语句是否可以即兼顾插入,又兼顾更新呢?即有则更新,无则插入。

ON DUPLICATE KEY UPDATE

在搜索过程中,第一个映入眼帘的就是ON DUPLICATE KEY UPDATE,完整语句为:

1
2
3
INSERT INTO table_name (id, `col`) VALUES('unique_id', 'insert_value') 
ON DUPLICATE KEY UPDATE
id = 'update_unique_id' col = 'update_value'

语句前面位正常的插入语句,后面则当相同唯一索引值或者主键已存在时,则按照之后的语句对数据进行更新。

按照我们的需求,语句应该这样写:

1
2
3
INSERT INTO table_name1 (id, `col`) VALUES('unique_id', 'insert_value') 
ON DUPLICATE KEY UPDATE
id = VALUES(id) col = VALUES(col)

WHERE NOT EXIST

还搜索到一种骚操作,先“预插入”(可能失败,可能成功),然后再更新,代码大概是下面这样子。

1
2
3
4
5
# 预插入
INSERT INTO table_name1 (id, col)
SELECT 1, 'col_value' FROM DUAL
WHERE NOT EXISTE (SELECT id FROM table_name1 WHERE id = 1) ;
UPDATE table_name1 SET id = col WHERE col = 'col_value';

这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

问题起源

因为团队之前没有Web前端,半路出家学了一点点VUE,生产环境魔改了一下花裤衩的vue-element-admin项目。之前系统中的上传一直不怎么好用,代码很凌乱。一碰到文件上传的需求内心总有一丝丝慌慌哒。

最近,小伙伴给我推了一个新的半成品框架:若依。拿过来一看,跟vue-element-ui很相似,相当于一个vue-element-admin+一些基础工具和扩展。特意看了一下其中的文件上传功能,也搭配着半年多于VUE,element-ui的磨合,借着这次更文,总结一下vue-element的文件上传方式。

自定义上传方法

在之前我们使用文件上传时,是将文件数据放置在一个form表单中,其中虽然用到了el-upload,单只将其用作一个文件获取组件。

实现代码如下:

VUE页面部分:

1
2
3
4
5
6
7
8
9
10
11
12
<el-upload
ref="Upload"
class="upload-demo"
:headers="uploadHeaders"
:file-list="files"
:auto-upload="false"
:multiple="false"
action="/xxxx/xxxx"
>
<el-button slot="trigger" size="small" type="primary">选取文件</el-button>
<el-button style="margin-left: 10px;" size="small" type="success" class="el-icon-upload" @click="submitUpload">上传</el-button>
</el-upload>

JavaScript部分:

1
2
3
4
5
6
7
8
9
10
11
12
submitUpload() {
// 开始执行上传
const form = this.$refs['uploadFile'].$el
const formData = new FormData(form)
formData.append('welfareId', this.welfareId)
formData.append('amount', this.amount)
// 上传方法
uploadMethods(formData).then(response => {
this.$message.success(response.data.msg)
this.$emit('after-upload')
})
}

上传方法部分代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export default function uploadServiceFunction(data) {
const config = {
headers: {
'Content-Type': 'multipart/form-data'
}
}
config.headers['token'] = 'xxxxx'

let baseurl = data.url
if (process.env.NODE_ENV === 'development') {
baseurl = getBaseUrl() + data.url
} else {
baseurl = process.env.VUE_APP_BASE_API + data.url
}
return axios.post(baseurl, data.formData, config)
}

这种方式繁琐,且在上传方法部分代码中,还需要特意区分开发环境与生产环境前缀(见端代码的倒数3-7行),并且在显示效果上,也看不到上传进度,没有很好的使用上el-upload的显示效果。嗯,那时候真是太菜了!

el-upload.submit

其实el-upload中已经有很好的配置,包括配置headers,配置其他信息字段的data字段等。

现在一般这么上传文件:

1
2
3
4
5
6
7
8
9
10
11
<el-upload
ref="uploadRef"
class="avatar-uploader"
:action="uploadUrl"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload"
:data="qiniuData"
>
<!-- 控件内容 -->
</el-upload>

若非自动上传,则配置手动上传方法这样写:

1
2
3
actionUpload() {
this.$refs.uploadRef.submit()
}

这样在文件上传时,即可看到el-upload封装好的文件实时上传进度与结果。Nice!

这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战

问题起源

好久没写文章,回顾文章发现之前“纯CSS绘制一个美美哒月亮”我是让单个月亮动起来,看到好多小伙伴能绘制美美的月绕地旋转,地绕日旋转,很漂亮,好像还有一篇嫦娥绕月的。自己也想实现一番。

又联想到最近读的《CSS Secret》(CSS揭秘)中,有一节“沿环形路径平移的路径”。动手动手。

上手思路

首先,利用animationrotate将图形旋转起来,然后再考虑如何使它保持平移方式。上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<!DOCTYPE html>

<head>
<title>环形平移动画</title>
</head>

<body>
<div class="cirl-box">
<img class="avtar"
src="https://p3-passport.byteacctimg.com/img/user-avatar/985fdb8019434c98a2d1ef549dc59fef~300x300.image" />
</div>
</body>
<style>
body {
width: 100%;
height: 100vh;
background: lightblue;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.cirl-box {
border: 2px solid black;
width: 400px;
height: 400px;
border-radius: 50%;
/* margin: 100px 100px; */
}

.avtar {
width: 7.5rem;
height: 7.5rem;
background-color: #f9f9f9;
border-radius: 50%;
object-fit: cover;
animation: spin 3s infinite linear;
transform-origin: 200px 200px;
}

@keyframes spin {
to {
transform: rotate(1turn);
}
}

</style>

</html>

起始位置如图:

image.png

动画效果如下图:

1b5e3040-b298-46eb-a911-55b4e776fb37.gif

可以看到随着头像旋转,头像本身也在随着旋转,从而使得头像在其他位置“歪”了!按照书中的说法,这叫做:“没有保持自己本来的朝向”。

实现平移的一种方式时,对头像本身加另一个动画,使得其向相反的方向旋转一定的角度,如下图:

042cdf87-11be-4839-a530-ddd32c708214.gif

如果将两个动画均加载上去,效果如何呢,看效果:

f073ff1c-e0cf-40e6-9284-c616b6b0b511.gif

哇,实现了!

上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<!DOCTYPE html>

<head>
<title>环形平移动画</title>
</head>

<body>
<div class="cirl-box">
<div class="cirle-move">
<img class="avtar"
src="https://p3-passport.byteacctimg.com/img/user-avatar/985fdb8019434c98a2d1ef549dc59fef~300x300.image" />
</div>
</div>
</body>
<style>
body {
width: 100%;
height: 100vh;
background: lightblue;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}

.cirl-box {
border: 2px solid black;
width: 400px;
height: 400px;
border-radius: 50%;
/* margin: 100px 100px; */
}

.avtar {
width: 7.5rem;
height: 7.5rem;
background-color: #f9f9f9;
border-radius: 50%;
object-fit: cover;
animation: spin-reverse 3s infinite linear;
}

.cirle-move {
animation: spin 3s infinite linear;
transform-origin: 200px 200px;
}

@keyframes spin {
to {
transform: rotate(1turn);
}
}

@keyframes spin-reverse {
to {
transform: rotate(-1turn);
}
}

</style>

</html>

代码优化

根据《CSS Secret》的说法,代码不够简练,并且后续内容让我们了解到一个新的CSS动画属性:animation-direction ,

在MDN的官方解释中说:

1
animation-direction CSS 属性指示动画是否反向播放

可选值有:normal, reverse, alternate, alternate-reverse。

1
2
3
4
5
6
7
normal:每个循环内动画向前循环,换言之,每个动画循环结束,动画重置到起点重新开始,这是默认属性。

alternate:动画交替反向运行,反向运行时,动画按步后退,同时,带时间功能的函数也反向,比如,`ease-in` 在反向时成为 `ease-out`。计数取决于开始时是奇数迭代还是偶数迭代

reverse:反向运行动画,每周期结束动画由尾到头运行。

alternate-reverse:反向交替, 反向开始交替。动画第一次运行时是反向的,然后下一次是正向,后面依次循环。决定奇数次或偶数次的计数从1开始。

我们只使用一个旋转动画来看看各个值的效果。

normal

f035f9ed-5521-4df4-8846-ca2aef4ec93a.gif

reverse

16f5ca18-c031-49a2-99a2-6be5af9c22e5.gif

alternate

ed46fcdd-ab5e-4dd9-904f-198afe60bdac.gif

alternate-reverse:与alternate的差别在于第一次的起始方向不同。

2431efe6-364c-4fde-af8c-291c22214084.gif

那么如何使用这一个值简化代码呢?将子元素的动画属性设置为继承:inherit,只通过设置一个animation-direction: reverse即可实现,效果与上文实现效果一模一样,就不再贴图了。

这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战

需求描述

在最近一次的实际生产项目中,需要紧急开发一个全屏播放窗体,设计大概是这个样子:

image.png

需求描述为,点击视频或图片,将其放大或者播放在上图中的播放窗口中;下方区域居中显示对于图片或者视频的描述文字;黑色背景区域显示为覆盖全屏的黑色半透明窗体;右上角有播放关闭按钮,关闭后即关闭整个播放区域和黑色窗体背景;并且要求覆盖浏览器的返回按钮,返回时效果同点击关闭按钮。

z-index的思路

最直接的想法,写一个组件,调用时组件的z-index设置为一个比较大的值。但是实际上,z-index使用是有局限性的。

在MDN官方文档中,z-index属性设定了一个定位元素及其后代元素或 flex 项目的 z-order。 当元素之间重叠的时候, z-index 较大的元素会覆盖较小的元素在上层进行显示。

需要注意以下几点:

  1. z-index只在当前堆叠的上下文中的层级,不同父元素的子元素之间进行显示时,会根据父级元素的z-index进行渲染;

  2. 可以为负值;

  3. 必须在position属性为:relative, absolute, fixed, sticky中才生效;

    因此,有时单纯为了修改层级,而避免修改DOM的position还需要为z-index添加单独的DOM元素,甚至无法添加;当然,更多情况是,页面元素复杂,单纯使用z-index可能需要逐级修改父级的z-index,改动和记录量较大。

    也是因此,我们放弃了这一单纯使用z-index的思路。(实际是单纯使用z-index没有达到预期效果,总有几个东东在飘在页面上方,手动狗头,所以不单纯是告诉大家,也是自己做一下记录)

body.append思路

即,创建组件时,改变组件的父级节点,直接将组件挂载在最外层的DOM树——<body></body>上,话不多数,上关键代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mounted() {
this.$nextTick(() => {
const body = document.querySelector('body')
if (body.append) {
body.append(this.$el)
} else {
body.appendChild(this.$el)
}
})
},
destroyed() {
const body = document.querySelector('body')
body.removeChild(this.$el)
},

通过上述代码,将该组件与系统现有的复杂层级组件抽离,从而达到置顶显示覆盖的最终效果。给自己点个赞!

通过这一思路,我们可以打开思路,即通过JS随意调整组件的挂载位置与层级,开不开心^_^。

完整代码如下:(其中一些class没有列出来,只是页面布局相关,如项目统计的左右边距,就不贴出来了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
<template>
<div class="popContainer">
<div style="width: 100%;height: 100%;" class="flex-col-center-end">
<div class="main-area top-info center" style="height: 88%">
<div style="width: 100%;height: 75%">
<slot name="main" />
</div>
</div>
<div class="bottom-info main-area center">
<slot name="bottom" />
</div>
</div>
<el-image :src="require('./叉.png')" class="close-icon pointer" @click="close" />
</div>
</template>

<script>
export default {
name: 'ModelFullScreen',
data() {
return {
show: false
}
},
watch: {
show() {
this.$emit('input', this.show)
}
},
created() {
this.show = this.value
},
mounted() {
this.$nextTick(() => {
const body = document.querySelector('body')
if (body.append) {
body.append(this.$el)
} else {
body.appendChild(this.$el)
}
console.log(body)
})
// ----------------------------后退相关------------------------------------
// 挂载完成后,判断浏览器是否支持popstate
if (window.history && window.history.pushState) {
window.history.pushState(null, null, document.URL) // 这里有没有都无所谓,最好是有以防万一
window.addEventListener('popstate', this.goBack, false)
// 回退时执行goback方法
}
},
destroyed() {
// const body = document.querySelector('body')
// body.removeChild(this.$el)
// 页面销毁时,取消监听。否则其他vue路由页面也会被监听
window.removeEventListener('popstate', this.goBack, false)
this.goBack()
},
methods: {
close() {
const body = document.querySelector('body')
body.removeChild(this.$el)
this.$emit('close')
},
goBack() {
const body = document.querySelector('body')
if (body) {
body.removeChild(this.$el)
this.$emit('close')
}
window.history.pushState(null, null, document.URL)
}
}
}
</script>

<style scoped>
.popContainer {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: auto;
margin: 0;
z-index: 50000;
background: rgba(0, 0, 0, 0.9);
}

.close-icon {
position: fixed;
top: 10%;
right: 10%;
z-index: 50001;
}

.top-info {
width: 100%;
flex: 1;
}

.bottom-info {
width: 100%;
background: black;
color: #AFAFAF;
height: 12%;

font-size: 14px;
font-family: PingFang SC;
font-weight: 400;
}

</style>

我正在参加中秋创意投稿大赛,详情请看:中秋创意投稿大赛

大佬们各显神通,绘制的月亮和动画一个比一个漂亮,特来学习。

圆月基调

月亮是晚上出现的,所以背景自然是黑色。

画圆咱们就不多说了,一个border-radius: 50%搞定。

基本框架代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html>
<title>
只画月亮
</title>
<body>
<div class="moon"></div>
</body>
<style>
body {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
background: black;
}

.moon {
width: 200px;
height: 200px;
background: yellow;
border-radius: 50%;
}
</style>
</html>

基本展示效果如图:

image.png

嗯,初步效果看着很不错,尤其我这种近视眼,月晕都不用加了…

月光晕

使用box-shadow可以添加阴影效果,我们来试试看:

1
box-shadow: x偏移量 | y偏移量 | 阴影模糊半径 | 阴影扩散半径 | 阴影颜色;

经过尝试,我们设定两层的光晕:

1
box-shadow: 0 0 30px 0px yellow, 0 0 100px 0 white;

效果如下图所示:

image.png

让圆变得立体起来

目前发现有以下几种策略:

渐变色背景

比如我们将月亮的背景色设置为:

1
2
3
4
5
6
background-image: linear-gradient(
45deg,
lightyellow 0%,
yellow 90%,
yellow 100%
);

效果如下:

image.png

添加线条

大佬文章中学到的,添加弯曲线条:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
.moon {
// 其他代码
position: relative;
}
.line {
width: 100%;
height: 100%;
background-color: linear-gradient(
45deg,
lightyellow 0%,
yellow 90%,
yellow 100%
);
border: solid 5px white;
border-radius: 50%;
margin: -0.5px;
position: absolute;
box-sizing: border-box;
transform: rotateY(45deg);
}
.line2 {
width: 100%;
height: 100%;
background-color: linear-gradient(
45deg,
lightyellow 0%,
yellow 90%,
yellow 100%
);
border: solid 5px white;
border-radius: 50%;
margin: -0.5px;
position: absolute;
box-sizing: border-box;
transform: rotateY(60deg);
}

.line3 {
width: 100%;
height: 100%;
background-color: linear-gradient(
45deg,
lightyellow 0%,
yellow 90%,
yellow 100%
);
border: solid 5px white;
border-radius: 50%;
margin: -0.5px;
position: absolute;
box-sizing: border-box;
transform: rotateY(75deg);
}

image.png

旋转

我们尝试让这个月亮旋转起来,然而效果,嗯,像极了以前看到的扁扁银河系,

ef009a36-3609-4aa0-bfcf-afee33e0c0e1.gif

看来走偏了,应该添加一个固定的圆形背景,,或者说应该让线条转动起来,效果如下。嗯,还不错。

017d5b14-3699-47c0-9ed7-634724847f6d.gif

参考的站内文章:

  1. 原来3D感空间行星轨迹是这样画的

  2. 中秋节快乐,使用CSS3实现小火箭🚀绕月飞行

  3. 【中秋】纯CSS实现日地月的公转

https://e360.yale.edu/features/the-dream-of-co2-air-capture-edges-toward-reality

2021年9月份,冰岛将开启一个碳捕获项目——Orca

DAC-Small_Climeworks.jpg

Orca 由八个细长的类似于水箱的盒子组成,盒子被称为“收集器”,每一个大约有一辆拖拉机拖车的大小,每个盒子都装饰着 12 个旋转的风扇,在里面吸入一股气流。在收集器内,一种称为吸附剂的化学试剂将捕获飘过的空气中所含的二氧化碳。吸附剂的表面会定期填满。届时,需要释放其中的二氧化碳。在 Orca,这项任务是通过来自附近热液喷口的热量来完成的。提取的 CO2 将通过管道从收集箱输送到附近的处理设施,在那里与水混合并转移到地下深处的井中。

这是我参与8月更文挑战的第28天,活动详情查看:8月更文挑战

在文章CSS动画-调速函数一文中,我们初步了解了一下CSS调速函数animation-timing-function的作用,介绍了一个重要的调速函数参数cubic-bezier(x1, y1, x2, y2),即贝塞尔曲线。在学习animation-timing-functionMDN文档时,发现除了可以取贝塞尔曲线外,还有另外一个函数:steps

steps规范

具体规范如下:

1
steps(number_of_steps, direction)

其中,number_of_steps表示一个正整数,表示动画变化的步数/帧数,direction表示变化函数是否为左连续或右连续。steps(2, jump-start)的变化曲线如下所示:横轴表示动画的时间进度,竖轴表示动画的执行进度。

image.png

通过分析函数曲线,我们可以看到,在(0, 0)时,动画进度发生阶梯跳跃,即此时动画效果是瞬时完成的,相比通过贝塞尔曲线对动画进行平滑过度,什么情况下需要使用steps函数呢?

在《CSS Secret》中介绍了一种情况:逐帧动画效果

比如下图所示为一个人跳跃的逐帧动画图:

image.png

那么,如何通过CSS动画将这一动画演示出来呢?我们来尝试一下。

帧动画

首先来试一下如果使用贝塞尔曲线会是什么样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<title></title>
<body>
<div class="jump">

</div>
</body>
<style>
body {
/* 居中 */
width: 100%;
height: 100vh;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.jump {
height: 226px;
width: 112px;
background: url('./img/jump.png') 0 0;
overflow: hidden;
animation: loader 3s infinite;
}
@keyframes loader {
to { background-position: -1009px 0; }
}

</style>
</html>

实现效果如下图:

4d064d03-fd37-4400-b21f-dd4360d8d4b0.gif

不管是使用linear或如何调整贝塞尔曲线参数,总是无法达成理想的动画效果。我们来试试如果使用steps有什么效果:

1
animation: loader 1s infinite steps(9);

13f15ec5-0aba-4daf-91a9-2f6366a844fd.gif

(嗯,请稍微忽略因截图问题导致的漏出来的一个脚)

可以看到动画效果非常不错了。

至于另外一个参数direction,我借用另外一篇博客的一个动图,希望能帮助大家理解:

3949005326-57ec7d2e67e8d.gif

这是我参与8月更文挑战的第26天,活动详情查看:8月更文挑战

通过本月的一些文章:

纯CSS制作跳动的心

纯CSS制作一个小动画

如何使用vue的transition做动画效果

纯CSS实现奥运升国旗

纯CSS实现轮播图

纯CSS做旋转不断的效果

纯CSS实现文字闪烁效果

我们已经初步了解了如何使用CSS做一些简单的动画效果。

今天就CSS动画的一个参数“animation-timing-function”。

animation-timing-function

MDN定义:

animation-timing-function属性定义CSS动画在每一动画周期中执行的节奏。可能值为一或多个 timing-function (经过在线确认,现在更名为easing-function)

对于关键帧动画来说,timing function作用于一个关键帧周期而非整个动画周期,即从关键帧开始开始,到关键帧结束结束。

定义于一个关键帧区块的缓动函数(animation timing function)应用到改关键帧;另外,若该关键帧没有定义缓动函数,则使用定义于整个动画的缓动函数。

通俗来说,animation-timing-function定义了一个动画帧周期的速度变化曲线,在《CSS揭秘》(CSS Secret)一书中,将其翻译为“调速函数”。我们先来看一下几个常用的参数动画效果。

0cfc1232-a054-4b47-809f-11a5930ac702.gif

源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
<!DOCTYPE html>
<title>animation-timing-function</title>
<body>
<div class="box1 circle">ease</div>
<div class="box2 circle">linear</div>
<div class="box3 circle">ease-in</div>
<div class="box7 circle">ease-out</div>
<div class="box4 circle">ease-in-out</div>
<div class="box5 circle">step-start</div>
<div class="box6 circle">step-end</div>
</body>
<style>
body {
width: 100%;
height: 100vh;
}
.circle {
width: 100px;
height: 100px;
border-radius: 50%;
background: yellowgreen;
margin: 20px 0;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.box1 {
animation: move1 5s linear infinite alternate;
}
.box2 {
animation: move2 5s linear infinite alternate;
}
.box3 {
animation: move3 5s ease-in infinite alternate;
}
.box4 {
animation: move4 5s ease-in-out infinite alternate;
}
.box5 {
animation: move5 5s step-start infinite alternate;
}
.box6 {
animation: move6 5s step-end infinite alternate;
}
.box7 {
animation: move7 5s ease-out infinite alternate;
}

@keyframes move1 {
100% {
transform: translate(1000px, 0);
}
}
@keyframes move2 {
100% {
transform: translate(1000px, 0);
}
}
@keyframes move3 {
100% {
transform: translate(1000px, 0);
}
}
@keyframes move4 {
100% {
transform: translate(1000px, 0);
}
}
@keyframes move5 {
100% {
transform: translate(1000px, 0);
}
}
@keyframes move6 {
100% {
transform: translate(1000px, 0);
}
}
@keyframes move7 {
100% {
transform: translate(1000px, 0);
}
}

</style>
</html>

从上到下,参数依次为ease(默认值), linearease-inease-outease-in-out以及step-startstep-end,其分别表示的含义为:

含义
ease 默认。动画以低速开始,然后加快,在结束前变慢。
linear 动画从头到尾的速度是相同的。
ease-in 动画以低速开始。
ease-out 动画以低速结束。
ease-in-out 动画以低速开始和结束。
step-start 直接跳转到动画最终状态
step-end 保持在动画开始状态

在MDNanimation-timing-function也有一个对应的例子:

855babc1-4953-4121-a375-f8823f8905c0.gif

前几个参数涉及到一个重要的函数,叫“贝塞尔曲线”。《CSS揭秘》中对贝塞尔曲线的介绍:

这种曲线由一定数量的路径片断所组成,各个片断的每一端都可以由一个手柄来控制曲率(这些手柄通常也被称作控制锚点)。一条复杂的曲线可能包含很多个片断,这些片断的端点彼此相连构成了整条曲线

image.png

如下图所示,就是端点在(0, 0)和(0, 1)的一条三次方贝塞尔曲线,横坐标代表时间,纵坐标代表动画进度。通过两个两个控制点可以完全自由的控制这条动画变化曲线。如下图两个控制点(锚点)绘制出的贝塞尔曲线如下图所示。

image.png

ease 等价于 cubic-bezier(0.25, 0.1, 0.25, 0.1)

cubic-bezier(x1, y1, x2, y2)

这个函数要求x1, x2在范围[0, 1]内,因为x代表时间,我们无法穿越时间范围定义动画开始之前以及动画结束之后的时间。当然,y1, y2是可以超出范围的,感兴趣可以试试如果将y1, y2设置为负数为有什么效果。

https://finance.sina.com.cn/jjxw/2021-04-13/doc-ikmyaawa9479622.shtml

有多少读者对这个话题感兴趣?有办法打动他们吗?这一点小小的规划,让阅读量有了很大的改变。

追求正确的想法意味着拒绝错误的想法

https://e360.yale.edu/features/the-dream-of-co2-air-capture-edges-toward-reality

碳捕获技术,碳中和,可以写一篇资讯类文章

前端工具库 https://www.thosefree.com/

动画 https://animate.style/

hack news https://news.ycombinator.com/news

stackoverflow 2021 overview 代表了技术趋势,值得一看