canvas踩坑

canvas 踩坑記

前言

原來以為還不想這么快接觸canvas的,可是最近因為業(yè)務(wù)需要,不得不提前開始接觸canvas了。

需求是根據(jù)后臺傳回來的圖片和文字進行合成圖片,然后保存到本地

雖然可以根據(jù)現(xiàn)有的插件html2canvas,但是畢竟第一次接觸,也想了解下canvas的 api,所以還是自己動手寫寫咯

要實現(xiàn)的最后的樣子如下,就大概畫了個草圖:

實現(xiàn)草圖

實現(xiàn)這樣的內(nèi)容,其實也還算簡單,使用到的 api 也不多,主要就兩個,一個是繪制文本的fillText,一個是繪制圖片的drawImage。

踩坑過程

圖片位置計算

因為需要依次獲取圖片,好計算下一張圖片的顯示位置,所以這里簡單使用promise去加載圖片,并且將當(dāng)前的圖片對象返回,之后好記錄些數(shù)據(jù):

function loadImageSync(src) {
  return new Promise(resolve => {
    let img = new Image()
    img.onload = () => {
      resolve(img)
    }
    img.src = src
  })
}

然后就可以這樣使用了:

// 假設(shè)img_src1和img_src2是后臺獲取到的
let { img_src1, img_src2 } = this.data
let img1 = null
loadImageSync(img_src1)
  .then(img => {
    img1 = img
    ctx.drawImage(img1, 0, 0)
    return loadImageSync(img_src2)
  })
  .then(img => {
    ctx.drawImage(img, 0, img1.height)
  })

這樣每張圖片的繪制高度就容易計算了。

文本換行

因為bgImage部分還要顯示一段文本內(nèi)容,而文本內(nèi)容也是從后臺獲取后來的,具體有多少字都是不清楚的,然后也不能全部放在一行,畢竟canvas沒有提供文字換行的 api。。不過好在有measureText這個方法,這個方法可以返回文字的寬度,可以利用這點做個計算:

function textAutoLine(text, canvas, initX, initY, lineHeight) {
  let ctx = canvas.getContext('2d')
  // 等同于css的font
  ctx.font = 'bold 18px Microsoft Yahei'
  // 初始化一行字體的寬度為0
  let lineWidth = 0
  let canvasWidth = canvas.width
  // 初始化截取位置為0
  let lastSubStrIndex = 0
  for (let i = 0; i < text.length; i++) {
    // 循環(huán)計算每個字的寬度 并累加到行寬上
    lineWidth += ctx.measureText(text[i]).width
    // 判斷行寬是否到達一個臨界值 也就是: 容器寬 - 文字第一個字的位置 - 臨界值(可自定義調(diào)整)
    if (lineWidth > canvasWidth - (initX + 40)) {
      // 一行達到臨界,開始繪制,并且重置行寬,另起一行,開始繪制下一行
      ctx.fillText(text.substring(lastSubStrIndex, i), initX, initY)
      initY += lineHeight
      lineWidth = 0
      lastSubStrIndex = i
    }
    if (i === text.length - 1) {
      // 第二行字 再做偏移
      ctx.fillText(text.substring(lastSubStrIndex, i + 1), initX + 40, initY)
    }
  }
}

繪制 dom

底部內(nèi)容是后臺返回的一段html代碼,因為是個dom片段,所以使用canvas繪制起來就相當(dāng)麻煩了,經(jīng)資料查找后發(fā)現(xiàn)可以通過特殊的方式,將dom繪制成圖片:

function drawDomToImg(dom) {
  let domData = `<svg xmlns="http://www.w3.org/2000/svg" width="500" height="200">
                      <foreignObject width="100%" height="100%">
                        <div xmlns="http://www.w3.org/1999/xhtml">${dom}</div>
                      </foreignObject></svg>`
  let DOMURL = window.URL || window.webkitURL || window
  let svg = new Blob([domData], { type: 'image/svg+xml;charset=utf-8' })
  let src = DOMURL.createObjectURL(svg)
  return loadImageSync(src)
}

這樣就能將一段dom也繪制成圖片了,最后我們將繪制后的路徑傳到loadImageSync中,也能得到這個圖片的信息了,比如寬和高。

圖片跨域

整個內(nèi)容都繪制完成之后,要將其生成圖片,使用canvastoDataURL就可以完成,不過這個操作出現(xiàn)了個問題,就是出現(xiàn)了圖片跨域的問題。

經(jīng)查找資料后發(fā)現(xiàn),在使用canvas繪制的時候,圖片的資源是本地或者外鏈都是可以的,但是生成圖片的時候,使用外鏈的圖片無法繪制進canvas中,可能是因為canvas的同源策略的保護機制吧。

解決方式需要兩點,一個是改造我們之前的loadImageSync的方法,加入一句代碼:

function loadImageSync(src) {
  return new Promise(resolve => {
    let img = new Image()
    // 加入這句
    img.setAttribute('crossOrigin', 'Anonymous')
    img.onload = () => {
      resolve(img)
    }
    img.src = src
  })
}

還有一個就是需要后臺設(shè)置CORS了,就是設(shè)置Access-Control-Allow-Origin: *,允許跨域。這樣就可以使用canvas生成圖片了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,351評論 25 708
  • 1、通過CocoaPods安裝項目名稱項目信息 AFNetworking網(wǎng)絡(luò)請求組件 FMDB本地數(shù)據(jù)庫組件 SD...
    陽明AI閱讀 16,236評論 3 119
  • 2018年6月22日,星期五,小雨 夢中,被轟隆轟隆的機器聲震醒,不用想,又是住上屋的鄰居開著耕田機在門前田里耕田...
    東梨梨閱讀 674評論 12 11
  • 小時候要快樂很簡單,長大后要簡單才快樂。
    開心沫沫閱讀 201評論 0 2
  • 文/風(fēng)雨綫 時間/ 2017年2月20日 前晚,與康表弟閑逛,天南海北、天馬行空般閑聊,不知怎的,說到新疆...
    風(fēng)雨綫閱讀 365評論 3 5

友情鏈接更多精彩內(nèi)容