原生JS實現影片截圖

發表於2023-11-14

影片截圖效果預覽

影片截圖效果

利用Canvas進行截圖

要用原生js實現影片截圖,可以利用canvas的繪圖功能 ctx.drawImage,只需要獲取到影片標籤,就可以透過drawImage把影片當前幀影像繪製在canvas畫布上。

const video = document.querySelector('video')
const canvas = document.createElement('canvas')
const w = video.videoWidth
const h = video.videoHeight
canvas.width = w
canvas.height = h
const ctx = canvas.getContext('2d')
ctx.drawImage(video, 0, 0, w, h)

接下來,需要把畫布轉化為圖片,canvas提供了兩個2D轉換為圖片的方法:canvas.toDataURL()canvas.toBlob()

canvas.toDataURL(mimeType, qualityArgument)方法

toDataURL可以把圖片轉換成base64格式的圖片,是一個同步方法,使用很簡單,在上面已經繪製好畫布的基礎上,只需要下面一行程式碼就可以獲取到當前影片幀的截圖了

const imageUrl = canvas.toDataURL("image/png")
console.log(imageUrl)

toDataURL生成的base64圖片

可以看到,它最終生成了一個很長字串的base64圖片地址。

canvas.toBlob(callback, mimeType, qualityArgument)方法

這個方法相比上一個方法的優點是它是非同步的,所以有一個callback回撥,這個callback回撥方法預設的第一個引數就是轉換好的blob檔案資訊,本文也想重點介紹這種方法的使用

先說明一下這個方法的三個引數:

引數 型別 是否必傳 說明
callback Function toBlob()方法執行成功後的回撥方法,支援一個引數,表示當前轉換的Blob物件
mimeType String 表示需要轉換的影像的mimeType型別。預設值是image/png,還可以是image/jpeg,甚至image/webp(前提瀏覽器支援)等
qualityArgument Number 表示轉換的圖片質量。範圍是0到1。由於Canvas的toBlob()方法轉PNG是無損的,因此,此引數預設是沒有效的,除非,指定圖片mimeType是image/jpeg或者image/webp,此時預設壓縮值是0.92

使用寫法如下:

canvas.toBlob((blob) => {
  console.log(blob)
}, 'image/png', 0.92)

可以看到方法執行得到的是當前轉換的Blob物件
Blob物件

那麼剩下的就是要將此Blob物件進一步轉化為可供img顯示的圖片地址。

將Blob物件轉化為圖片地址

下面介紹三種方法進行轉化:

方式一: 透過URL.createObjectURL()方法將Blob轉化為URL

canvas.toBlob((blob) => {
  const imageUrl = URL.createObjectURL(blob)
  console.log(1, imageUrl)
}, 'image/jpeg', 1)

如下圖所示,轉化得到的是一個bold流的圖片地址。

blob流圖片地址

方式二: 透過FileReader將Blob轉化為DataURL

canvas.toBlob((blob) => {
  const reader = new FileReader()
  reader.readAsDataURL(blob)
  reader.onload = () => {
    const imageUrl = reader.result
    console.log(2, imageUrl)
  }
}, 'image/webp', 1)

如下圖所示,轉化得到的是一個base64的圖片地址。

base64圖片地址

方式三: 透過ajax將Blob上傳到伺服器

canvas.toBlob((blob) => {
    const formData = new FormData()
    formData.append('file', blob) // 這裡的'file'是介面接收引數的欄位名,需要根據實際情況改變
    const xhr = new XMLHttpRequest()
    xhr.onload = () => {
      const imageUrl = JSON.parse(xhr.responseText).data // 介面回撥引數,需要根據實際情況處理
      console.log(3, imageUrl)
    }
    xhr.open('POST', '/api/upload', true) // '/api/upload'是上傳介面,需要根據實際情況改變
    xhr.send(formData)
}, 'image/webp', 1)

由此就會將圖片上傳到你的檔案伺服器裡,最終可以得到一個你自己檔案伺服器下對應的圖片地址。

toBlob()方法的相容

首先,toBlob()方法IE9瀏覽器不支援,因為Blob資料格式IE10+才支援。

然後,對於IE瀏覽器,toBlob()的相容性有些奇怪,IE10瀏覽器支援ms私有字首的toBlob()方法,完整方法名稱是msToBlob()。而IE11+,toBlob()方法卻不支援。

但是,我們可以基於toDataURL()方法進行polyfill,效能相對會差一些,JavaScript程式碼如下:

if (!HTMLCanvasElement.prototype.toBlob) {
  Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
    value: function (callback, type, quality) {
      var canvas = this
      setTimeout(function() {
        var binStr = atob( canvas.toDataURL(type, quality).split(',')[1] )
        var len = binStr.length
        var arr = new Uint8Array(len)

        for (var i = 0; i < len; i++) {
          arr[i] = binStr.charCodeAt(i)
        }

        callback(new Blob([arr], { type: type || 'image/png' }))
      })
    }
  })
}

注意事項

使用外部連結播放影片的話需要在影片標籤上設定允許跨域的處理,新增屬性crossOrigin='anonymous'即可,

<video className="videoTag" crossOrigin='anonymous' controls>
     <source src="https://www.w3school.com.cn/example/html5/mov_bbb.mp4" type='video/mp4' />
</video>

或者,在js裡處理

const video = document.querySelector(".videoTag")
video.setAttribute('crossOrigin', 'anonymous')
video.load()

否則會報以下錯誤:
跨域報錯

完整封裝示例

最後,給出一個利用toBlob進行影片截圖,最終獲取base64圖片地址的封裝方法,程式碼示例如下:

function getBase64ByVideo(video) {
    const canvas = document.createElement("canvas")
    const w = video.videoWidth
    const h = video.videoHeight
    canvas.width = w
    canvas.height = h
    return new Promise((resolve, reject) => { // 由於toBlob方法是非同步的,所以這裡用Promise
      const ctx = canvas.getContext('2d')
      ctx.drawImage(video, 0, 0, w, h)
      canvas.toBlob((blob) => {
        // 透過FileReader將Blob轉化為DataURL
        const reader = new FileReader()
        reader.readAsDataURL(blob)
        reader.onload = () => {
          const imageUrl = reader.result
          resolve(imageUrl)
        }
      }, 'image/webp', 1) // 根據需要可以自行配置這裡的兩個引數
    })
}

呼叫方法:

const videoTag = document.querySelector(".videoTag")
const dataUrl = await getBase64ByVideo(videoTag)

相關文章