参考资料:
预览效果: https://tzc123.github.io/cursor_special_effects/
在这个年代, 不用 Chrome 都不好意思说自己是敲代码的. 特别是前端, Chrome 对于前端来说简直是调试利器, 不可或缺的存在. 不得不说 Chrome 的功能是极其强大的, 其中最亮眼的功能莫过于扩展程序 (浏览器插件), 国内各大浏览器品牌也都纷纷 "效仿", 今天就为大家带来一次 Chrome 插件开发实践.
准备工作
创建一个文件夹 cursor_special_effects
在文件夹中创建 manifest.JSON 文件, 文件内容如下
- {
- "manifest_version": 2,
- "name": "爆炸吧, 小鼠标!",
- "version": "0.0.1",
- "description": "小鼠标在线爆炸",
- "author": "田某人",
- "content_scripts": [{
- "matches": ["*://*/*"], // 匹配所有的网站
- "js": ["index.js"] // 插件的主要代码
- }]
- }
正式开始
创建好 index.JS 文件, 就可以开始紧张刺激的编程了.
我们编写的插件是可以获取到原页面的 dom 元素的, 而且插件只在 Chrome 上安装, 就不用考虑该死的兼容了, 可以随心所欲的使用一些 ES 新特性. 当然 Chrome 插件同样能在 360 浏览器, 百度浏览器上安装的.
首先分析下需求, 需要实现鼠标点击特效, 我们的插件需要哪些功能
使用 canvas 覆盖在原网页上
canvas 不能影响原网页的响应时间, 所以加上
pointer-events: none;
由于只需要一个 canvas, 所以就直接使用 JS 创建:
- class CursorSpecialEffects {
- constructor() {
- this.computerCanvas = document.createElement('canvas')
- this.renderCanvas = document.createElement('canvas')
- this.computerContext = this.computerCanvas.getContext('2d')
- this.renderContext = this.renderCanvas.getContext('2d')
- }
- // 初始化
- init() {
- // 设置 canvas 样式
- const style = this.renderCanvas.style
- style.position = 'fixed'
- style.top = style.left = 0
- style.zIndex = '999999999999999999999999999999999999999999'
- style.pointerEvents = 'none'
- style.width = this.renderCanvas.width = this.computerCanvas.width = this.globalWidth
- style.height = this.renderCanvas.height = this.computerCanvas.height = this.globalHeight
- // 挂载到页面上
- document.body.append(this.renderCanvas)
- }
- }
- const cursorSpecialEffects = new CursorSpecialEffects()
- cursorSpecialEffects.init()
这里采用离屏渲染, 即一个 canvas 用来计算, 一个 canvas 用来渲染.
现在场景就布置完成了, 现在需要添加鼠标的点击事件, 每次点击都触发一次特效.
- class CursorSpecialEffects {
- constructor() {
- ...
- this.runing = false // 标识特效是否在运行
- this.booms = [] // 可以同时存在多个特效, 所以使用数组来保存
- }
- init() {
- ...
- Windows.addEventListener('mousedown', this.handleMouseDown.bind(this))
- }
- // 鼠标点击事件
- handleMouseDown() {
- const boom = new Boom({
- // 爆炸的原点
- origin: { x: e.clientX, y: e.clientY },
- // canvas 上下文
- context: this.computerContext,
- // 场景区域, 当特效超出场景范围时, 就应该停止了
- area: {
- width: this.globalWidth,
- height: this.globalHeight
- }
- })
- boom.init()
- this.booms.push(boom)
- // 如果特效已经在运行, 则不重复开始
- this.running || this.run()
- }
- run() {
- // 特效已经开始了
- this.running = true
- if (this.booms.length == 0) {
- // 如果所有的爆炸都消失了, 则特效停止
- return this.running = false
- }
- // 每一帧都运行一次, 刷新动画
- requestAnimationFrame(this.run.bind(this))
- // 每次绘制之前都先清空画布
- this.computerContext.clearRect(0, 0, this.globalWidth, this.globalHeight)
- this.renderContext.clearRect(0, 0, this.globalWidth, this.globalHeight)
- this.booms.forEach((boom, index) => {
- // 如果爆炸停止, 则将它从特效中移除
- if (boom.stop) {
- return this.booms.splice(index, 1)
- }
- // 爆炸每有一点进展, 就绘制一次
- boom.move()
- boom.draw()
- })
- // 一帧绘制完毕, 将计算使用的 canvas 绘制到页面的 canvas 上
- this.renderContext.drawImage(this.computerCanvas, 0, 0, this.globalWidth, this.globalHeight)
- }
- }
这里引入了一个 Boom 类, 每次鼠标点击都会创建一个 Boom 实例, 直到这个实例播放完成, 才会被删除. 这个 Boom 类可以有很多实现方式, 不同的实现方式可以实现不同的特效, 前提是这个 Boom 类需要提供 move,draw 函数和 stop 属性. move 用于推进特效进行下去, draw 用来对每一帧进行绘制, stop 用来表示特效是否停止.
接下来介绍一种 boom 的实现方式:
- class Boom {
- constructor ({ origin, context, circleCount = 20, area }) {
- // 爆炸的原点
- this.origin = origin
- // canvas 上下文
- this.context = context
- // 小球的数量
- this.circleCount = circleCount
- // 显示的区域
- this.area = area
- // 默认停止
- this.stop = false
- // 小球
- this.circles = []
- }
- // 通过数组取随机值
- randomArray(range) {
- const length = range.length
- const randomIndex = Math.floor(length * Math.random())
- return range[randomIndex]
- }
- // 随机颜色
- randomColor() {
- const range = ['8', '9', 'A', 'B', 'C', 'D', 'E', 'F']
- return '#' + this.randomArray(range) + this.randomArray(range) + this.randomArray(range) + this.randomArray(range) + this.randomArray(range) + this.randomArray(range)
- }
- // 随机一个范围内的值
- randomRange(start, end) {
- return (end - start) * Math.random() + start
- }
- // 初始化
- init() {
- // 创建小球
- for(let i = 0; i <this.circleCount; i++) {
- const circle = new Circle({
- context: this.context,
- origin: this.origin,
- color: this.randomColor(),
- angle: this.randomRange(Math.PI - 1, Math.PI + 1),
- speed: this.randomRange(1, 6)
- })
- this.circles.push(circle)
- }
- }
- move() {
- // 循环推进每个小球的运动
- this.circles.forEach((circle, index) => {
- // 小球如果超过了可视范围, 就删除该球
- if (circle.position.x> this.area.width || circle.position.y> this.area.height) {
- return this.circles.splice(index, 1)
- }
- circle.move()
- })
- // 如果所有的小球都被删除, 就把这个 boom 标记为停止, 等待下一帧被删除
- if (this.circles.length == 0) {
- this.stop = true
- }
- }
- draw() {
- // 循环绘制每个小球
- this.circles.forEach(circle => circle.draw())
- }
- }
这样 Boom 类就实现了, 但是到现在还是不知道特效到底是什么样子. 这里引入了一个 Circle 类, 具体实现特效的任务落到了 Circle 类身上. 将 Circle 类抽象出来辅助 Boom 类, 有助于梳理代码逻辑.
- class Circle {
- constructor({ origin, speed, color, angle, context }) {
- this.origin = origin
- // 小球的起始位置为原点
- this.position = { ...this.origin }
- // 小球的颜色
- this.color = color
- // 小球的速度
- this.speed = speed
- // 小球发射的角度
- this.angle = angle
- this.context = context
- // 绘制的帧数
- this.renderCount = 0
- }
- draw() {
- // 通过颜色, 位置, 绘制小球
- this.context.fillStyle = this.color
- this.context.beginPath()
- this.context.arc(this.position.x, this.position.y, 2, 0, Math.PI * 2)
- this.context.fill()
- }
- move() {
- // 小球移动
- this.position.x = (Math.sin(this.angle) * this.speed) + this.position.x
- this.position.y = (Math.cos(this.angle) * this.speed) + this.position.y + (this.renderCount * 0.3)
- this.renderCount++
- }
- }
这里需要解释的是小球的移动规则, 根据角度和速度计算出每一帧小球移动的横坐标 Math.sin(this.angle) * this.speed, 纵坐标 Math.cos(this.angle) * this.speed, 再加上原本的坐标. 为了实现万有引力, 将 0.3 设置为重力加速度.
大功告成
接下来只需要进入 Chrome 的扩展程序页面, 点击打包扩展程序 (没有这个按钮的, 需要打开开发者模式), 选择 cursor_special_effects 文件夹进行打包就可以了.
打包成功后, 我们可以在
cursor_special_effects
文件夹的同级目录下看到
- cursor_special_effects.pem
- ,
- cursor_special_effects.crx
两个文件, 前面一个是秘钥, 后面一个就是打包后的文件了. 双击或者把. crx 文件拖到扩展程序页面即可安装. 如果 Chrome 安装不了, 那就是因为没有发布, 使用 360 浏览器一样可以安装.
安装后刷新页面, 效果如下:
看起来还不错, 但是因为发布需要 5 刀的开发费用, 所以就算了, 就当是闲着无聊的写的小玩意, 之后应该会做些更有意思的特效.
GitHub: https://github.com/tzc123/cursor_special_effects
来源: https://juejin.im/post/5bea918c51882516b9377c4f