前言
通常我们开发微信小程序,需要产品经理需要我们前端来写一些动画(小声bb,用 svg 图片不就好了🙃)效果如下
可能你会觉得这很简单不是,使用 <svg> 去绘制不就行了,很遗憾,微信小程序到现在还不支持 svg
没办法,我们只能采取折中的办法,使用 canvas 绘制线条,css 绘制动画
实现
因为是在微信小程序中实现这样的路径动画,为了快速构建,我们使用uniapp
并且方便在 vscode 中开发我们可以使用 unibest 这个快速开发模板,来加快我们的构建
创建 path-animation 项目
pnpm create unibest my-project
cd path-animation
pnpm i
pnpm dev:mp-weixin
使用微信小程序开发者工具进入目录中,打开 mp-weixin 文件,去掉云服务,设置项目名为 path-animation
好了,我们看下效果
修改下首页文件 index.vue
ok 😃,我们先来使用 canvas 来画一条直线
<route lang="json5" type="page">
{
layout: 'default',
style: {
navigationBarTitleText: '',
},
}
</route>
<template>
<div
class="w-300px h-300px flex justify-between items-center flex-col m-auto mb-30px position-relative mt-80rpx"
>
<canvas
id="my-canvas"
canvas-id="my-canvas"
class="position-absolute w-300px h-300px z-0"
></canvas>
</div>
</template>
<script lang="ts" setup>
const context = uni.createCanvasContext('my-canvas')
context.setStrokeStyle('#FBD3A7') // 设置 填充的颜色
context.moveTo(50, 150) // 设置 绘制起始坐标
context.lineTo(250, 150) // 设置 绘制结束坐标
context.stroke() // stroke() 方法会实际地绘制出通过 moveTo() 和 lineTo() 方法定义的路径
context.draw() // 绘制
</script>
<style lang="scss" scoped>
//
</style>
微信小程序的 canvas 一定要设置 canvas-id不然会出问题,我们就设置和 id 一样就好了
使用 uni.createCanvasContext(元素的id) 来获取访问的 <canvas> 对象
setStrokeStyle设置 线的颜色moveTo设置 绘制起始坐标lineTo设置 绘制结束坐标strokestroke方法会实际地绘制出通过moveTo()和lineTo()方法定义的路径draw根据设置绘制图像
看一下效果,不错 😄,线条已经出来了
如果直接使用 canvas 来绘制运动动画,需要计算距离,如果有弧度还得计算贝塞尔曲线很是麻烦
转换个思路 🤔,我们可以使用 css 动画来实现运动
<route lang="json5" type="page">
{
layout: 'default',
style: {
navigationBarTitleText: '',
},
}
</route>
<template>
<div
class="w-300px h-300px flex justify-between items-center flex-col m-auto mb-30px position-relative mt-80rpx"
>
<canvas
id="my-canvas"
canvas-id="my-canvas"
class="position-absolute w-300px h-300px z-0"
></canvas>
<div class="triangle"></div>
</div>
</template>
<script lang="ts" setup>
const context = uni.createCanvasContext('my-canvas')
context.setStrokeStyle('#FBD3A7') // 设置 填充的颜色
context.moveTo(50, 150) // 设置 绘制起始坐标
context.lineTo(250, 150) // 设置 绘制结束坐标
context.stroke() // stroke() 方法会实际地绘制出通过 moveTo() 和 lineTo() 方法定义的路径
context.draw() // 绘制
</script>
<style lang="scss" scoped>
.triangle {
position: absolute;
width: 8px;
height: 8px;
clip-path: polygon(0 0, 100% 50%, 0 100%);
background: #fbd3a7;
animation: move 3000ms infinite linear;
offset-path: path('M -100 150 L 100 150');
}
@keyframes move {
0% {
offset-distance: 0%;
}
100% {
offset-distance: 100%;
}
}
</style>
使用 clip-path 的 polygon 主要用于绘制多边形,当然 clip-path 还有很多其他有趣的属性可以看看
polygon 的使用也很简单,polygon(x1 y1, x2 y2, x3 y3, x4 y4, xn yn)
每两个点就是绘制的多边形的一个点,polygon(0 0, 100% 50%, 0 100%) 绘制三个点就是一个三角形,background 填充背景色
offset-path 是这个运动动画的核心,用于定于一个运动路径
- M 定义路径起始点
- L 定义路径结束点
最后使用 animation 定义运动动画
当然这是直线运动,那曲线运动呢,该怎么实现呢?🤔
<route lang="json5" type="page">
{
layout: 'default',
style: {
navigationBarTitleText: '',
},
}
</route>
<template>
<div
class="w-300px h-300px flex justify-between items-center flex-col m-auto mb-30px position-relative mt-80rpx"
>
<canvas
id="my-canvas"
canvas-id="my-canvas"
class="position-absolute w-300px h-300px z-0"
></canvas>
<div class="triangle"></div>
</div>
</template>
<script lang="ts" setup>
const context = uni.createCanvasContext('my-canvas')
context.setStrokeStyle('#FBD3A7') // 设置 填充的颜色
context.moveTo(50, 150) // 设置 绘制起始坐标
context.lineTo(140, 150) // 设置 绘制结束坐标
context.quadraticCurveTo(150, 150, 150, 160)
context.lineTo(150, 200)
context.stroke() // stroke() 方法会实际地绘制出通过 moveTo() 和 lineTo() 方法定义的路径
context.draw() // 绘制
</script>
<style lang="scss" scoped>
.triangle {
position: absolute;
width: 8px;
height: 8px;
clip-path: polygon(0 0, 100% 50%, 0 100%);
background: #fbd3a7;
animation: move 3000ms infinite linear;
offset-path: path('M -100 150 L -10 150 Q 4 150 4 160 L 4 200');
}
@keyframes move {
0% {
offset-distance: 0%;
}
100% {
offset-distance: 100%;
}
}
</style>
如果有多个lineTo,除了最后一个是结束坐标,其他都是绘制的中途点
quadraticCurveTo(控制点,结束点),使用这个绘制二次贝赛尔曲线,起始点就是上一步绘制的中途点,也就是 lineTo(140,150)这个点
css 动画部分也就修改了下路径而已,其中 Q 表示绘制贝赛尔曲线,其中 4 150 表示控制点,4 160 表示结束点,与 canvas 的 quadraticCurveTo 类似
想必你在看了第一个动画的时候就会想,既然都是 canvas 路径 与 offset-path 路径,都是路径那会不会是一样的参数 🤨
我们来对比下,canvas 路径 与 offset-path 路径
canvas 与 offset-path 各自的 5个点分别对应图上的 1~5
但是我们看实际坐标
这样按顺序一个个对比下来,我们发现只有 x 坐标不同,y 坐标都是一样的
为什么会这样呢?🤔
其实啊,是因为 canvas 与 offset-path 的初始坐标系不同
所以,坐标才会变成
-
50 -> -100
-
140 -> -10
-
150 -> 4
-
150 -> 4
-
150 -> 4
前两个换算还算正确,最后三个为什么不是 0 呢?
是因为啊,我们需要加上 元素自身宽度的一半,8 - 4 = 4,这样三角形才会居中在线上
这样太麻烦了,有没有什么办法不用换算直接就能用的方法
因为位移元素元素是 position: absolute ,那么是不是可以使用 left、top 来定义元素初始位置呢?
.triangle {
position: absolute;
top: 0;
left: 0;
width: 8px;
height: 8px;
clip-path: polygon(0 0, 100% 50%, 0 100%);
background: #fbd3a7;
animation: move 3000ms infinite linear;
offset-path: path('M 50 150 L 140 150 Q 150 150 150 160 L 150 200');
}
完整案例代码
<route lang="json5" type="page">
{
layout: 'default',
style: {
navigationBarTitleText: '',
},
}
</route>
<template>
<div
class="w-300px h-300px flex justify-between items-center flex-col m-auto mb-30px position-relative mt-80rpx b-1px b-solid b-red"
>
<canvas
id="my-canvas"
canvas-id="my-canvas"
class="position-absolute w-300px h-300px z-0"
></canvas>
<div class="triangle"></div>
</div>
</template>
<script lang="ts" setup>
const context = uni.createCanvasContext('my-canvas')
context.setStrokeStyle('#FBD3A7') // 设置 填充的颜色
context.moveTo(50, 150) // 设置 绘制起始坐标
context.lineTo(140, 150) // 设置 中途点
context.quadraticCurveTo(150, 150, 150, 160) // 绘制二次贝赛尔曲线
context.lineTo(150, 200) // 绘制结束坐标
context.stroke() // stroke() 方法会实际地绘制出通过 moveTo() 和 lineTo() 方法定义的路径
context.draw() // 绘制
</script>
<style lang="scss" scoped>
.triangle {
position: absolute;
top: 0;
left: 0;
width: 8px;
height: 8px;
clip-path: polygon(0 0, 100% 50%, 0 100%);
background: #fbd3a7;
animation: move 3000ms infinite linear;
offset-path: path('M 50 150 L 140 150 Q 150 150 150 160 L 150 200');
}
@keyframes move {
0% {
offset-distance: 0%;
}
100% {
offset-distance: 100%;
}
}
</style>
案例代码在路径动画
小结
因为微信小程序不支持 svg,所以我们采用 canvas + css 来代替
使用 canvas 这几个属性进行绘制
setStrokeStyle设置 线的颜色moveTo设置 绘制起始坐标lineTo设置 绘制结束坐标strokestroke方法会实际地绘制出通过moveTo()和lineTo()方法定义的路径draw根据设置绘制图像
运动元素,采用 position: absolute top: 0 left: 0 进行定位,确定 offset-path位移的初始点为 (0,0)
这样就可以和 canvas 的路径一样了
最后我们使用 animation 定义一下动画就可以了