如何實現一個 滑鼠點選特效的 chrome外掛

一副鍵盤定乾坤發表於2018-11-13

如何實現一個 滑鼠點選特效的 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資料夾進行打包就可以了。

如何實現一個 滑鼠點選特效的 chrome外掛
打包成功後,我們可以在cursor_special_effects資料夾的同級目錄下看到cursor_special_effects.pemcursor_special_effects.crx兩個檔案,前面一個是祕鑰,後面一個就是打包後的檔案了。雙擊或者把.crx檔案拖到擴充套件程式頁面即可安裝。如果chrome安裝不了,那就是因為沒有釋出,使用360瀏覽器一樣可以安裝。

如何實現一個 滑鼠點選特效的 chrome外掛

安裝後重新整理頁面,效果如下:

如何實現一個 滑鼠點選特效的 chrome外掛

看起來還不錯,但是因為釋出需要5刀的開發費用,所以就算了,就當是閒著無聊的寫的小玩意,之後應該會做些更有意思的特效。

github: github.com/tzc123/curs…

相關文章