《你並不老是須要html2canvas》將會是系列文章,逐步講解如何使用canvas原生api來實現咱們的截圖需求,文章將圍繞不使用html2canvas須要解決的一系列問題來展開,指望能給你們帶來以下收穫:javascript
相信H5截圖分享的功能你們都作過,並且會反覆作。凡是反覆作的事情,我都會想:「下一次怎麼作得更好,或者更快」,畢竟我是一個「懶惰」而又「沒有耐心」的人。懶惰驅動我提升生產力,沒有耐心驅使我在一樣一件事情上不斷挖掘新的東西,不然容易在重複中失去耐心。html
說到截圖,大名鼎鼎的html2canvas - Screenshots with javascript你們確定耳熟能詳,強大,易用。感謝開源社區的貢獻,確實幫咱們節省了不少力氣。前端
// 使用示例
html2canvas(document.querySelector("#capture")).then(canvas => {
document.body.appendChild(canvas)
});
複製代碼
但在使用過程當中我也遇到了一些問題:html5
所以,簡單分析截圖分享的需求(如上圖所示)後,我決定試試本身直接繪製,因而就開始了個人踩坑之旅。若是你也想爲了一點點的體積優化而不使用html2canvas,那請作好心理準備解決下列問題:java
本系列文章將逐步補充和解決我作各類各樣的截圖需求遇到的問題,也歡迎你們補充到留言裏面,我會抽空解決。git
做爲系列文章的第一篇,和你們介紹了一下出發點,後續再也不贅述,下面進到正題。github
截圖適配,主要指佈局適配、圖片分辨率和字體大小適配。衆所周知,不一樣的設備顯示尺寸、顯示精度可能不同,因此一樣的代碼在不一樣的設備上運行會顯示不徹底同樣的圖像,會致使以下截屏問題:chrome
375px
寬的屏幕居中顯示175px
寬的圖片時,圖片left = 100px
,而600px
寬的屏幕居中顯示同樣的圖片(圖片尺寸爲175 * 600/375 = 280px
)時,圖片left = (600 - 280) / 2 = 160px
;解決問題前,咱們先明確咱們的截圖分享需求:生成一張「看起來」同樣的圖片。canvas
爲了看起來同樣,咱們首先會肯定一個截圖的尺寸,以750px
視覺稿爲基準,假設截圖是750 * 1280
。視覺稿給的750px
是以iPhone6爲基準的兩倍圖,因此在iPhone6上完美顯示,可是iPhoneX應該使用三倍圖,所以750 * 1280
在iPhoneX上顯示會以爲有些模糊(特別是文字,認真看能看到鋸齒),所以咱們有兩個解決方法:後端
方案 | 優勢 | 缺點 |
---|---|---|
1. 全部設備都按照三倍圖的尺寸繪製(甚至更大尺寸) | 生成的圖片徹底一致 | canvas繪製圖片越大,耗時越長 |
2. 不一樣設備安裝本身的顯示精度進行繪製 | 在當前設備顯示清晰 | 分享出去的圖片在更高顯示精度的設備上會感受到模糊 |
兩種方案能夠按需挑選,假設須要頻繁繪製,優先選擇方案2,假設只須要繪製1次,而且對繪製內容有較高的視覺要求,優先選擇方案1。方案2能夠理解爲方案1的一種特例,下面重點說方案1的實現,你們能夠推導出方案1怎麼實現:
750px
視覺稿爲基準,根據當前屏幕尺寸和屏幕顯示精度計算視覺稿到屏幕的縮放關係:scale = window.innerWidth / 750 * window.devicePixelRatio
;scale
進行縮放便可;window.innerWidth = 375
, window.devicePixelRatio = 3
;理解了適配的原理以後,具體的編碼工做就水到渠成了,下面主要是補充操做細節。
繪製圖片的第一步是加載圖片,只有在圖片加載完成後,才能把圖片內容繪製到canvas上。其次,出於瀏覽器內容安全考慮,僅有支持跨域訪問的圖片才能從canvas上導出成base64編碼。
// 預加載圖片 & 跨域設置
const imgs = ['avatar.png', 'bg_screenshot.jpg', 'container.png'];
const imgEls = {};
let loadCount = 0;
const imgLoad = (callback) => {
loadCount ++;
if (loadCount === imgs.length) callback();
}
const preloadImg = (callback) => {
imgs.forEach((imgUrl) => {
let img = document.createElement('img');
img.crossOrigin = 'Anonymous'; // 【重要】設置跨域,服務器須要返回跨域支持
img.onload = ()=>{
imgEls[imgUrl] = img;
imgLoad(callback);
};
img.src = './img/' + imgUrl;
});
}
// 使用
preloadImg(() => {
console.log('img loaded!');
});
複製代碼
注意:
img.crossOrigin = 'Annoymous
只是將該圖片請求設置爲一個跨域請求,這個時候,若是服務器沒有設置合適的CORS,瀏覽器會提示圖片加載失敗,所以這裏須要後端配合設置:Access-Control-Allow-Origin:*
。
高清圖這個概念和兩倍圖、三倍圖的概念同樣,不理解的同窗能夠去搜索一下。舉個例子,iPhone6下window.devicePixelRatio = 2
, 而window.innerWidth = 375
,所以須要給750px
寬的視覺稿才能在手機上看到和視覺稿同樣的效果。而iPhoneX是三倍圖,所以須要375 * 3
的背景圖,可是視覺設計師通常只會給二倍圖,因此這個時候在iPhoneX下圖片顯示沒有視覺稿那麼清晰,若是對視覺有要求,還須要設計師給三倍圖(除非是矢量圖,咱們能夠自由縮放)。
理解了高清圖以後,咱們截屏的canvas大小就能夠計算出來了,和高清圖同樣,在window.devicePixelRatio = 2
的設備上,咱們會將canvas設置成window.innerWidth
的兩倍,最後生成的圖片就至關因而兩倍圖,能在該設備清晰顯示。
在canvas大小已知的基礎上,咱們就能夠從視覺稿中量出繪製元素的寬度,left,top信息,直接縮放繪製到canvas上了。
const scale = window.innerWidth / 750 * window.devicePixelRatio; // 【重要】以750px(視覺稿給的寬度每每是750px)爲基準計算canvas的寬度
const ratio = 1.7; // 截圖的高寬比
const width = 750 * scale;
const height = width * ratio;
screenshotCanvas = document.createElement('canvas');
screenshotCanvas.width = width;
screenshotCanvas.height = height;
const ctx = screenshotCanvas.getContext('2d');
ctx.drawImage(imgEls['container.png'], (750 - 643) / 2 * scale, 184 * scale, 634 * scale, 843 * scale ); //【重要】 1. 圖片水平居中,top從視覺稿中量出來是184 2. 634 * 843是視覺稿中圖片的尺寸
複製代碼
微信後臺給到的用戶頭像都是方形的,若是須要繪製圓形頭像,能夠先設置頭像圓形區域,裁剪後進行繪製,這個時候只有在圓形區域的內容纔會被繪製進去,而後恢復ctx設置(ctx.save() / ctx.restore()
)繼續繪製別的內容便可。
ctx.save();
const avatarR = 103 * scale / 2; // 頭像半徑
ctx.arc(540 * scale + avatarR, 244 * scale + avatarR, avatarR, 0, Math.PI * 2, false); // 設置特定區域
ctx.clip(); // 裁剪區域,僅繪製特定區域內的內容
ctx.drawImage(imgEls['avatar.png'], 540 * scale, 244 * scale, 103 * scale, 103 * scale );
ctx.restore();
複製代碼
文字大小咱們常常用rem
來適配,可是canvas繪製只支持px
單位,所以須要咱們本身計算,其實和上面的高清圖同樣,等比縮放便可:
const getFont = (size) => {
return size * scale + 'px serif'; // 能夠按需設置字體
};
ctx.textAlign = 'center';
ctx.textBaseline = 'middle'; // 【重要】 文本設置垂直居中,默認爲向上對齊,可是不一樣瀏覽器向上對齊的表現不同,所以使用垂直居中,下面的591.5, 380至關於文案的中心點位置
ctx.font = getFont(26); // 【重要】計算字號大小,視覺稿上字號大小爲26,實際大小爲26 * scale
複製代碼
看上面的註釋你們應該也看到了,關於字體的垂直位置設置有個坑,字體繪製的時候,默認是頂部對齊的,可是不一樣瀏覽器頂部對齊的表現不一樣,所以改用垂直居中。假設原來的top是400,字體高度爲100,那設置ctx.textBaseline = 'middle'
後,繪製字體的y位置就是400 + 100 / 2 = 450
。 參考:Canvas文字繪製top位置不一致問題
截圖分享需求,大都是繪製圖片、分享文案和用戶信息,不少時候並不須要出動html2canvas這種「重型武器」,瞭解如何作適配和查閱繪製相關的api,你也能夠輕鬆實現截圖需求。
前方還有不少問題等着咱們去解決,例如文字繪製怎麼局部高亮、自動換行和自動省略等等,下一篇博客見,同時期待與你們共同成長。
附:demo源碼
寫完後發現系統相關推薦的一篇文章講截圖(主要用了html2canvas)講得比較全面,這裏引用一下,你們能夠結合起來學習: