听说可达鸭特别火?六一教你怎么用可达鸭生产表情包
听说你们没抢到可达鸭?不要紧!通过这个网站你就可以生成自己的可达鸭表情包啦:demo(请用PC端访问,svg在移动端上的兼容性太渣渣了,踩坑QAQ)
整体的demo分为画鸭子、生成gif、发布静态站点这三大块,让我们来逐步看看是怎么实现生成定制鸭鸭表情包的网站的嘎!
画鸭子
我们的可达鸭采用SVG进行绘制。采用SVG的原因是相比canvas,比较容易做手部的动画,喜欢3d的同学可以直接上three.js来做。
整个画面分为可达鸭、收音机、地面、贴纸4个部分。
可达鸭
可达鸭是整个画面中最复杂的部分,主要在于可达鸭的图形复杂,并且许多部分都是曲线的path围成的。
svg中画曲线主要有以下几种方式:
- 椭圆弧线,svg中用A表示,需要起始点坐标,x轴、y轴半径,起始x轴方向,角度大小,旋转方向和重点坐标几个参数,对高中解析几何的复习非常有帮助。我们的可达鸭身体就是由两个半椭圆弧拼接而成的。
- 二次贝塞尔曲线,svg中用Q表示,需要起始点坐标一个控制点,起始点、终止点分别到控制点的连线就是起始、终止部分的斜率,中间斜率平滑变更,就形成了曲线。我们可达鸭的脚部就全部都是用二次贝塞尔曲线实现的。
- 三次贝塞尔曲线,svg中用C表示,相比二次贝塞尔曲线多了一个控制点。我们可达鸭的手和嘴就有一部分是用三次贝塞尔曲线实现的。
而像可达鸭的头发(圆角矩形)、眼睛(椭圆)则直接用svg的rect、ellipse、circle等图形画成的。
其实,像可达鸭这种左右对称的结构,很多部分,如手、眼、脚等都可以用defs来定义模板然后用use引用的方式产出。读者有兴趣的话可以试试改造下我们的代码。
收音机、地面与贴纸
画完可达鸭后,收音机、地面与贴纸就显得相对容易许多,因为整体都是规则图形。
收音机就是上圆角矩形-矩形-下圆角矩形3个图形拼接,在加上顶部同样类似圆角矩形的天线、矩形的按钮与圆形的喇叭。由于svg的rect不支持仅上部或下部变成圆角,因此我们通过4条边与两段圆弧线拼接而成。
而地面则更容易了,整体是两个椭圆+3朵小花,每朵小花都是由4瓣圆形花瓣+一个圆形花心构成。为了方便计算,我们绘制花瓣的时候都将花瓣放置成水平垂直的方向,并将花瓣+花心打包在一个g中,在g上直接用transform: rotate整体旋转。
贴纸则是直接通过text与rect拼成的。这里唯一的点在于我们希望text相对于rect是居中对齐的,而text是不支持类似text-align:center或transform: translate这样的居中方式的。但它提供了text-anchor和baseline-anchor这两个属性,让我们可以控制文本的锚点。我们将两个属性都设为了middle。
动画
由于我们使用的是svg,动画就显得非常简单,我们直接使用css animation属性定义手和贴纸的动画。回顾下animation对应的属性:
- animation-name: 我们需要定义的keyframe的名字,这里叫duckHandLeft与duckHandRight
- animation-duration: 时长,这里设置为2秒
- animation-timing-function: 动画时间曲线,为了简单我们就直接采用了内置的ease
- animation-delay: 延迟,这里设为0s
- animation-iteration-count:循环次数,这里设置为infinite
- animation-direction:动画方向,我们设置为alternate,这样我们只要在keyframe中定义一个方向的动画,让浏览器自动帮我们补充逆向的动画
- animation-fill-mode:动画补充模式,我们设置为both让图形一开始就在起始位置。由于我们的动画是infinite因此backwards能起到相同的效果
- animation-play-state:是否暂停,我们不需要暂停。
生成GIF
在生成gif上我们选择了gif.js库
选择这个库的原因为:
- star数比较多
- 这个库可以接受canvas作为帧
- API比较简洁
在上一part中,我们产出的是一个大大的svg,gif.js不能接受dom元素作为参数,那我们就需要先将svg转成canvas,这个时候就需要另一个库html-to-image出场了!
首先,我们需要将svg component用forwardRef包一层,接着,再在页面上放置一个看不到的svg用来生成动画帧,详细的生成的代码逻辑可以看注释。
useEffect(() => {
if (!exporting || !svgRef.current) {
return;
}
toCanvas(svgRef.current as unknown as HTMLElement).then((canvas) => {
canvasList.push(canvas);
// 当到最后一个帧时,开始导出gif
if (rotateDeg === MAX_ROTATE_DEG) {
const gif = new GIF({
width: 460,
height: 400,
workerScript: gifWorkerUrl,
});
// 增加正序动画帧
gif.addFrame(canvasList[0]);
for (let i = 0; i < canvasList.length; i++) {
gif.addFrame(canvasList[i], {
// 第一帧停留得比较久
delay: i === 0 ? BIG_DELAY : DELAY_STEP,
});
}
// 增加逆序动画帧
gif.addFrame(canvasList[canvasList.length - 1], { delay: BIG_DELAY });
for (let i = 0; i < canvasList.length; i++) {
gif.addFrame(canvasList[canvasList.length - 1 - i], {
delay: i === 0 ? BIG_DELAY : DELAY_STEP,
});
}
gif.addFrame(canvasList[0], { delay: BIG_DELAY });
gif.on("finished", function (blob) {
setExporting(false);
setCanvasList([]);
setRotateDeg(0);
// 下载生成好的gif
window.open(URL.createObjectURL(blob));
});
gif.render();
} else {
setRotateDeg(rotateDeg + STEP);
}
});
}, [rotateDeg, exporting]); 比较好奇的是,没有找到接入提供dom元素、动画时间长度、帧率就直接生成gif的库,我们讨论了一下,觉得如果实现这么一个库,问题在于:如果是比较复杂的dom元素,html-to-image需要一定时间,可能会导致页面卡住或者htmlToCanvas无法在规定帧率内完成,导致帧率变低或者丢帧。
如果要实现这个库的话,可以考虑在指定帧数内只记录下dom元素内的所有位置信息,把这些信息异步html-to-image,再使用gif.js生成gif图。(如果有同学有使用过这样的库,也可以评论一下
发布部署
在代码上传到github后,我们需要一个站点来展示静态资源~
首先想到的github自带提供的部署静态资源的功能,选择repository进入settings,可以看到有一个pages tab
但是实际尝试后觉得对于站点托管非常难用!(如果是我不会用的话可以在评论区指出
- 无法便捷的push代码就触发,只能配置actions
- 打包之后的相对路径很迷
- 文件夹只能选root或者docs,不可以指定文件夹
兜兜转转看到一篇比较好的介绍站点托管的文章https://github.com/lmk123/blog/issues/55
主要推荐这两个站点
Netlify和Vercel(也就now)我们都尝试了一下,都非常的顺滑
- 不收费(这很重要!!)
- 可以直接关联github
- build后deploy基本无需配置
- 监测push代码后自动job
大家可以根据偏好选择使用哪一个~本文的demo链接就是由Vercel托管的l
我们的github仓库地址:https://github.com/just-00/psyduck
基恩士成长空间 440人发布