以前看到這個掘金的文章今天這個仇先記下來了,以爲挺有趣的,可是他是基於別人已經封裝好的框架html2canvas來實現的。本着學習的態度,就用react和canvas原生api從新擼了一遍,在其中遇到了很多坑,也讓本身學習到了很多姿式。javascript
react
,ant-design
,styled-components
java
class Face extends Component {
constructor (props) {
super(props)
this.state = {
text: '2018年5月21日 沒人給我點贊,這個仇我先記下來了', // 控制文本框和輸出圖片的文案
canvasToDataURL: '' //把輸出圖片轉換成DataURL,用於新建一張圖片覆蓋在原來的canvas上面,使得移動端能夠長按保存圖片
}
this.img = null // 保存輸出部分的image dom實例,塞進canvas裏
this.canvas = null // 保存canvas dom實例
this.ctx = null // canvas上下文
}
}
複製代碼
{/* 輸入部分 */}
<div className='input-container'>
<img id="img" src={ require('../../../assets/grudges.png') } alt='記仇'/> <TextArea type='textarea' value={this.state.text} onChange={(e) => { this.onChangeText(e) }} placeholder='請輸入你的記仇咯~'> </TextArea> </div>
{/* 輸出部分 */}
<div className='display-container'>
{/* 咱們須要操做的canvas */}
<canvas id='canvas' width={200} height={205}></canvas>
{/* 覆蓋在canvas上面 */}
<img className='replace-img' src={this.state.canvasToDataURL} alt="from canvas"/>
</div>
複製代碼
輸入部分由初始圖片和輸入框組成,輸入框添加兩個props
,一個是value
,一個是onChange
。value
是組建的狀態text
,onChange
的時候會調用onChangeText
這個方法根據新的文案去更新canvas裏的文案。react
輸出部分由canvas和img組成,canvas用來處理圖像,img覆蓋在這個canvas上面,主要用於解決移動端不能直接長按canvas保存爲圖片的問題。用新的img覆蓋在canvas上面,移動端就能夠長按保存爲圖片。git
onChangeText (e) {
this.fillText(e.target.value) // 根據新值渲染新的文案
this.setState({
text: e.target.value,
canvasToDataURL: this.canvas.toDataURL('image/png') // 經過渲染文案後的canvas,經過toDataURL方法更新佔位img的src
})
}
複製代碼
async componentDidMount () {
this.img = document.querySelector('#img')
this.canvas = document.querySelector('#canvas')
this.ctx = this.canvas.getContext('2d')
await this.initCanvas() // 初始化canvas,把img塞進canvas裏
this.fillText(this.state.text) // 繪製文案
this.setState({
canvasToDataURL: this.canvas.toDataURL('image/png')
}) // 根據canvas裏面的圖像內容,生成DataURL,並更新給輸出部分的img的src,這樣輸出部分的img獲得了更新}
複製代碼
async initCanvas () {
this.ctx.clearRect(0, 0, this.width, this.height) // 清空canvas
this.ctx.fillStyle = 'white'
this.ctx.fillRect(0, 0, this.width, this.height)
const img = await this.loadImage('https://cdn.b1anker.com/grudges.png') // 建立一個新的img
this.drawImage(img)// 把img寫進canvas裏
return Promise.resolve()
}
複製代碼
在這裏就會遇到一個坑,就是個人圖片等資源是放到本身的cdn上,當使用canvas的getImageData之類的方法的時候會發生跨域的問題。github
解決辦法就是給img加個crossOrigin
屬性,值爲anonymous
,而且相應的資源的響應頭必須返回access-control-allow-origin: *
。web
具體能夠看跨域解決方案canvas
loadImage (url, crossOrigin = true) {
// 使用canvas以更好地處理回調地獄
return new Promise ((resolve, reject) => {
const img = document.createElement('img')
if (crossOrigin) {
img.crossOrigin = 'anonymous' // 添加crossOrigin以解決canvas跨域問題
}
img.onload = () => {
resolve(img)
}
img.onerror = (err) => {
reject(err)
}
img.src = url
})
}
複製代碼
這裏又遇到另一個坑,就是文字在canvas中的換行,不是那麼好操做。api
首先要經過canvas的measureText方法,測量text每一個字符的長度,而後累加,當長度超過文案最大的寬度時,就換行,而後從新累加。跨域
然而,事情並無那麼簡單,由於輸入的字符多是中文,英文,數字等,因此換行的時候,會有偏差,沒有達到理想的換行效果。
能夠看到,這個日
字,稍微有點往外邊飄了,具體的緣由就是在字符1
以前累加的長度並無超過文案最大的寬度,但這個時候已經很是接近文案最大寬度了,而在加上下一個字符日
後,由於日字是中文字符因此比數字英文字符之類的要長一點,最終究形成了這個日
字有點超出邊界。因此在累加的時候,還要計算下一個字符,再判斷加上下一個字符是否超過最大寬度減去4的差,若是超過則換行。至於爲何要減去4
,這個是考慮到了各類狀況啦(當前長度比較接近與最大寬度但爲超過最大寬度,可是下一個字符是數字的狀況)。
// 繪製文案
fillText (text) {
if (!text) {
this.initCanvas()
return
}
// 清除圖片如下的畫布空間,以從新繪製文案
this.ctx.clearRect(0, 155, 200, this.height - 155)
this.ctx.fillStyle = 'white'
this.ctx.fillRect(0, 155, 200, this.height - 155)
this.ctx.font = '14px sans-serif'
this.ctx.fillStyle = 'black'
const boundary = this.width - 20 // 最大文案寬度
let initHeight = 175 // 當前文案繪製距離canvas頂部的距離
let lastIndex = 0
let j = 0 // 記錄換行深度,若是深度大於canvas能展現的範圍,則對canvas進行resize(咱們這裏設置了從第三行開始擴展canvas)
for (let i = 0; i < text.length; i++) {
// 當前長度
const currTextWidth = this.ctx.measureText(text.substring(lastIndex, i)).width
// 加上下一次長度
const nextTextWidth = this.ctx.measureText(text.substring(lastIndex, i + 1)).width
if ( currTextWidth > boundary) {
// 繪製文案
this.ctx.fillText(text.substring(lastIndex, i), 8, initHeight)
// 增長繪製文案高度,爲換行作準備
initHeight += 20
lastIndex = i
j++
this.resize(j)
}
if (i === text.length - 1) { //繪製剩餘部分
this.ctx.fillText(text.substring(lastIndex, i + 1), 8, initHeight)
}
}
this.resize(j)
}
複製代碼
由於canvas簡單的改變寬高度會使內容發生變形等意向不到的狀況,因此對此要作一些處理
// 重置canvas大小
resize (deep) {
if (deep > 1) {
// 存儲當前canvas的圖片信息
const store = this.ctx.getImageData(0, 0, this.width, this.height)
this.ctx.clearRect(0, 0, this.width, this.height)
// 增長高度
this.canvas.setAttribute('height', 200 + (deep - 1) * 20)
this.ctx.fillStyle = 'white'
this.ctx.fillRect(0, 155, 200, this.height - 155)
this.ctx.font = '14px sans-serif'
this.ctx.fillStyle = 'black'
// 繪製以前存儲的圖片信息回canvas
this.ctx.putImageData(store, 0, 0)
}
}
複製代碼
等到部署在服務器上的時候,本覺得萬事大吉了,沒想到仍是出問題了。初次訪問的時候是ok的,可是刷新以後,就出問題了。有些瀏覽器沒問題,有些瀏覽器有問題。其中有問題的是iphone的safari,這個時候就用iphone連上mac進行調試。能夠發現瀏覽器報錯,居然是說圖片跨域了。但是咱們剛剛已經採起了相應的跨域解決方案,爲何仍是會跨域呢。經過右鍵複製爲curl到終端訪問,明明是能夠的,可是safari恰恰不行。估計是不一樣瀏覽器對於service worker的實現有必定的差別。
後來查閱了大量資料後,大概知道了緣由。多是由於service worker不支持動態的請求跨域資源,也就是跨域請求時service worker的request
的屬性mode
爲cors
可是屬性credentials
爲same-origin
,就會報錯了。因此要對動態請求的跨域資源作一些修正處理:
self.toolbox.router.get("/(.*)", function (request, values, options) {
let newRequest = null
if (request.mode === 'cors') {
/* 當腳本發起的動態跨域請求時,request.mode的值是'cors' * 這個時候須要從新實例化一個Request * 並設置credenti爲'include' * 用新的Request去代替原來的 */
const newRequest = new Request(request, {
credentials: 'include'
})
}
return self.toolbox.cacheFirst.apply(this, [newRequest || request, values, options])
}, {
origin: /cdn\.b1anker\.com/,
cache: {
name: staticAssetsCacheName,
maxEntries: maxEntries
}
})
複製代碼
- 這裏用到了sw-toolbox.js
- service worker中cors的介紹: origin is optional, and it's used to determine whether or not the response that's returned is opaque. If you leave this out, the response will be opaque, and the client will have limited access to the response's body and headers. If the request was made with mode: 'cors', then returning an opaque response will be treated as an error. However, if you specify a string value equal to the origin of the remote client (which can be obtained via event.origin), you're explicitly opting in to provides a CORS-enabled response to the client.
- credentials介紹:credentials 是Request接口的只讀屬性,用於表示用戶代理是否應該在跨域請求的狀況下從其餘域發送cookies.
此次編碼之旅花費了很多時間,可是也讓本身學到了不少東西。canvas的相關操做,cdn如何使用,圖片跨域,service work跨域怎麼解決等等...
第一個版本作的功能有些簡單,以後會考慮加入替換本地圖片,默認圖片庫等功能