如何实现一个 鼠标点击特效的 chrome插件

如何实现一个 鼠标点击特效的 chrome插件

参考资料:chajian.baidu.com/developer/e…

预览效果:tzc123.github.io/cursor_spec…

在这个年代,不用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() {
      	...
   		window.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.pemcursor_special_effects.crx两个文件,前面一个是秘钥,后面一个就是打包后的文件了。双击或者把 .crx文件拖到扩展程序页面即可安装。如果chrome安装不了,那就是因为没有发布,使用360浏览器一样可以安装。

安装后刷新页面,效果如下:

看起来还不错,但是因为发布需要5刀的开发费用,所以就算了,就当是闲着无聊的写的小玩意,之后应该会做些更有意思的特效。

github: github.com/tzc123/curs…

猜你喜欢

转载自juejin.im/post/5bea918c51882516b9377c4f