给掘金写了个有趣又好玩的一键三连插件 | 仿B站效果

语言: CN / TW / HK

theme: fancy

我正在参加「创意开发 投稿大赛」详情请看:掘金创意开发大赛来了!

先来看看b站的一键三连是什么效果:

2022-08-09 13.51.36.gif

不难观察出以下几个特点:

  1. 长按点赞出现抖动动画
  2. 长按点赞时关联按钮会有圆环进度条效果
  3. 长按超过一段时间后放开则一次实现三个动作并且有个绽放特效

接下来我们要做的就是逐步实现这些步骤,如何开始呢?这就需要介绍今天的主角:谷歌扩展插件。

创建一个Chrome插件

Chrome插件是一个用Web技术开发、用来增强浏览器功能的软件,它其实就是一个由HTML、CSS、JS、图片等资源组成的一个.crx后缀的压缩包。可以通过 chrome-plugin-demo 这个项目了解更多,这里我们直接讲如何使用:

首先在谷歌浏览器直接打开地址 chrome://extensions/ 进入扩展程序,并打开右上角开发者模式,这时就可以加载我们的插件了:

image.png

展程序会以 manifest.json 这个文件来识别并加载插件:

```json { "manifest_version": 2, "name": "掘金一键三连小助手", "version": "1.0", "description": "通过Chrome插件实现的一键三连效果,长按点赞3秒即可点赞+收藏+关注", "author": "ShawnPhang", "icons": { "48": "icon.png", "128": "icon.png" }, "page_action": { "default_icon": "icon.png", "default_title": "我是pageAction", "default_popup": "popup.html" }, "content_scripts": [ { "matches": ["http://juejin.cn/*"], "js": ["mojs.js", "inject.js"], "css": ["like.css"] } ], "background": { "scripts": ["background.js"] }, "web_accessible_resources": [] }

```

| 加载后效果 | 状态栏效果 | | --- | --- | | image.png | image.png |

在这个json文件中最主要看 content_scripts 这段配置,它表示了插件会向网页注入的JS文件和CSS文件,前面说了谷歌插件既是由一系列网页文件构成的,那么接下来就可以正式开始我们的效果实现了~

长按抖动

这是最容易实现的一个效果了,这里我定义了一个 shaking 的类,重复执行一段css动画,主要就是利用 translate 属性重新定位元素,就可以做到抖动的效果,下面的css也是非常随意写的,效果还可以。虽然只是上下左右位移却使用了 translate3d,是为了触发css的3d加速性能会更好。

``` .article-suspended-panel > .shaking { animation: shake 350ms linear; animation-iteration-count: infinite; animation-direction: reverse; }

@keyframes shake { 10%, 90% { transform: translate3d(-1px, 0, 0); } 15%, 85% { transform: translate3d(0, -1px, 0); } 20%, 80% { transform: translate3d(+2px, 0, 0); } 25%, 75% { transform: translate3d(0, +2px, 0); } 30%, 70% { transform: translate3d(-2.5px, 0, 0); } 35%, 65% { transform: translate3d(0, -2.5px, 0); } 2.50%, 60% { transform: translate3d(+2.5px, 0, 0); } 2.55%, 55% { transform: translate3d(0, +2.5px, 0); } 50% { transform: translate3d(-2.5px, -2.5px, 0); } } ```

接下来在 inject.js 中只需要捕获文章页面点赞按钮,为其添加 mousedown 的监听事件,在点下鼠标的时候动态添加上 shaking 这个class,动画就开始执行了:

```js const likeBtn = document.querySelector('.article-suspended-panel .panel-btn')

likeBtn?.addEventListener('mousedown', () => { if (likeBtn.className.includes('active')) { return } likeBtn.classList.add('shaking') }) ```

2022-08-09 22.12.14.gif

圆环进度条

这个效果开始有点难度了,需要分两个Div来画,我们都知道一个完整的圆是这样:

css .circle { width: 4rem; height: 4rem; border: 2px solid red; border-radius: 50%; box-sizing: border-box; }

image.png

这时我们先把红色边改为透明,然后只显示其中两条,并旋转一个角度,这就得到了半圆效果: css .circle { .... border: 2px solid transparent; border-top: 2px solid #1e80ff; border-right: 2px solid #1e80ff; transform: rotate(-135deg); }

image.png

现在以这个半圆我们先来绘制右半部分的圆环,在这个 circle 元素父级添加一个外层元素,使结构如下:

```html

```

外层的 wrapper 高度和圆环高度一致,宽度则为一半,绝对定位到右边,此时效果是这样的:

css .wrapper { width: 2rem; height: 4rem; position: absolute; right: 0; background: yellow; }

image.png

这时我们先写个css动画让 circle 转起来:

css .circle .rightcircle { right: 0; animation: circle 3s linear infinite; } @keyframes circle { 0% { transform: rotate(-135deg); } 50%, 100% { transform: rotate(45deg); } }

2022-08-09 22.42.10.gif

此时黄色区域为圆环的父级元素,如果我们将该区域视为可视区,那么只需要设置溢出隐藏:

css .wrapper { ..... overflow: hidden; } 效果就出来了:

2022-08-09 22.42.36.gif

同样的方法绘制左半圆,叠加在一块就形成了环形进度条动画,核心在于两个Div的动画执行时间是一致的,也就是完整跑完一个360°的旋转周期,只不过各自都有一半被遮住,从而形成了最终效果,下面有请码上掘金为我们演示完整代码:

代码片段

回到我们刚刚插件中,在点赞按钮点下的事件中我们需要批量添加上面这段DOM到相应的操作按钮中,然后绝对定位在左上角(0,0)处即可,查看网页源代码可知按钮宽高为 4rem,颜色我们则取全局变量中的蓝色 var(--juejin-brand-1-normal),将相关CSS写到 like.css 文件中后,JS中定义一个创建DOM的函数:

js function createCircle() { const fragment = document.createElement('div') fragment.classList.add('circle_process') fragment.innerHTML = `<div class="wrapper right"> <div class="circle rightcircle"></div> </div> <div class="wrapper left"> <div class="circle leftcircle"></div> </div>` return fragment } 因为这段结构还是有点多的,就不一一使用Element片段去创建了,创建完最外层的Div之后直接使用innerHtml写入两个半圆的结构,函数返回这个DOM片段。

下面就是在点击事件中处理相关动作,用时间戳判断长按时间是否持续3秒,用一个数组存储好创建的片段,鼠标抬起时循环调用其.remove()方法来移除DOM,b站的效果当然更加复杂,在这里并不是直接终止动画而是逆向运行动画,由于我们是使用css实现的,就折腾不了这种细节了,直接上代码:

```js let doms = [] let timeStamp = 0

const likeBtn = document.querySelector('.article-suspended-panel .panel-btn') likeBtn?.addEventListener('mousedown', () => { if (likeBtn.className.includes('active')) { return } timeStamp = new Date().getTime() / 1000 likeBtn.classList.add('shaking') doms.push(createCircle(), createCircle()) document.getElementsByClassName('panel-btn')[0].appendChild(doms[0]) document.getElementsByClassName('panel-btn')[2].appendChild(doms[1]) }) likeBtn?.addEventListener('mouseup', () => { likeBtn.click() const now = new Date().getTime() / 1000 const pass = now - timeStamp > 2.9 likeBtn.classList.remove('shaking') // 移除震动 for (const iterator of doms) { // 移除圆环 iterator.remove() } if (likeBtn.className.includes('active')) { return } if (pass) { // TODO: follow and collect btn action } }) ```

2022-08-09 23.11.58.gif

粒子绽放效果

这个效果用css实现难度更高了,刚好我今天在掘金看了一篇文章,介绍了一个轻量级动画库 mojs,里面的爆裂(Burst)效果就很适合这个场景,于是把umd文件下载来引入页面中。

inject.js 中定义一个函数执行来动画: js function play(parent, cb) { new window.mojs.Burst({ // 爆裂范围 {从多大 : 到多大} radius: { 0: 50 }, // 动画挂载的父元素, 如果不填默认挂载到 <body> parent, // 动画延迟的贝塞尔曲线函数 easing: mojs.easing.bezier(0.1, 1, 0.3, 1), // 动画延迟时间 duration: 1500, // 在动画动之前等待的时间 (这里一般设置150ms方便减少低端机型可能会存在的卡顿) delay: 300, // 扩散的粒子配置 children: { duration: 750, // 粒子大小变换 {从多大 : 到多大} // rand(from, to) rand函数可以帮我们随机出一个区间的值 radius: { 0: 'rand(5, 25)' }, // 形状选择, 这里我们选择了 “圆形” shape: 'circle', // 粒子可选的填充色 fill: ['#1abc9c', '#2ecc71', '#00cec9', '#3498db', '#9b59b6', '#fdcb6e', '#f1c40f', '#e67e22', '#e74c3c', '#e84393'], }, // 透明度 opacity: 0.6, // 生成的粒子数量 count: 12, onStart() { // 动画触发前的钩子函数 }, onComplete() { // 动画完成后的钩子函数 cb && cb() }, }).play() } 接着在 mouseup 事件中调用:

js .......... likeBtn?.addEventListener('mouseup', () => { ............. if (pass) { play(likeBtn, () => { // 这里只有两个按钮所以直接用回调函数来排列动画了 play(document.getElementsByClassName('panel-btn')[2]) }) } })

完整效果展示(后面长按时间调整到2s):

2022-08-10 17.22.52.gif

插件完整代码地址:chromePlugin-juejin-oneThree