canvas圖片問題淺析

問題

Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.

'xxx' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

這是我寫canvas圖片業(yè)務遇到的兩個問題帜篇。

前言

正文較長,結論在最后。
本文適合正在接觸canvas圖片業(yè)務的前端同學;當然,沒接觸過的仗颈,提前看看有哪些坑也是極好的:D
歡迎讀完后打臉(比如說哪些地方沒說明白啦,哪些地方存在知識點問題啦)!

正文

一昧旨、
?先簡單說下跟本文相關的需求:涂鴉板里能嵌圖片;能把圖片導出祥得;由于有多張圖兔沃,為了讓體驗更好還需要有個預加載方案。

Paste_Image.png

寫demo的時候我用的本地圖片级及,調canvas toDataURL方法并沒有報錯乒疏。

但是在聯(lián)調的時候,換成外域圖片饮焦,卻報錯了:

Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.

按慣例去stackoverflow上查了查怕吴,找到了解決方案(詳情可以看這里):

var img = new Image();
img.setAttribute('crossOrigin', 'anonymous');
img.src = url;

當時沒想那么多,加進去試試再說县踢,不出意料地解決了問題转绷,不禁再次感嘆so大法好!

然而在加了圖片預加載代碼之后硼啤,發(fā)現(xiàn)有的圖片就加載不出來了暇咆,打開控制臺報錯:


Paste_Image.png

開始以為是圖片服務器那邊沒有設CORS,聯(lián)系那邊說設了丙曙;然后說「你們怎么用的源站域名爸业,源站的域名可能導致種種問題,改用CDN域名試試」亏镰,但發(fā)現(xiàn)還是有問題扯旷。然后逐步定位到是圖片預加載代碼的問題,改了之后似乎?就好了索抓。

好景不長钧忽,后來由于?QA哥哥的一個「誤操作」,又出現(xiàn)了同樣的問題逼肯,我的內心是崩潰的耸黑。。

二篮幢、
上面簡單地說了下我遇到問題與解決問題(趕進度)的過程大刊,接下來要入坑辣~

先說說 Tainted canvases may not be exported 的問題。對于外域圖片三椿,?瀏覽器仍然是允許你畫到canvas上的缺菌,但是toDataURL就會報錯(toBlob也是)葫辐。為什么會這樣呢?

This protects users from having private data exposed by using images to pull information from remote web sites without permission.

上面這段引用?摘抄自這里伴郁。在對應的語境里耿战,大意就是說:如果你請求外域的圖片without permission,可能會暴露你的隱私數據焊傅,所以瀏覽器為了保護你的隱私會限制這樣的請求剂陡。

「wtf?請求外域圖片怎么就會暴露我的隱私數據了??」其實我也不明白狐胎,這個坑請先自己填一下鸭栖,之后會補充。

那么怎么繞過瀏覽器的「關照」呢顽爹?答案是?:你允許就行了~而img.setAttribute('crossOrigin', 'anonymous');就是告訴瀏覽器,我允許?骆姐!

再說說'xxx' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.镜粤。

這個報錯的根源是:

var img = new Image();
img.setAttribute('crossOrigin', 'anonymous');
img.src = url;  // 外域url

(這個異常實際上在控制臺里是拿不到調用棧的,瀏覽器并不會告訴你是這里出了問題)

這個異常信息本身是說「reponse header中不帶Access-Control-Allow-Origin(以下簡稱AC)這個字段玻褪,所以'xxx'被同源策略阻止了?」肉渴。

(如果你想進一步了解同源策略,可以看看阮老師的這篇文章带射。)

這時候你可能會想起同规,我之前不加img.setAttribute('crossOrigin', 'anonymous');,也去請求外域圖片窟社,怎么就沒報過錯券勺?

這里我簡單補充一下?:img.setAttribute('crossOrigin', 'anonymous');加了這句灿里,就意味著你這次的圖片請求變成了CORS請求关炼,就要受同源策略的限制了(而這個報錯就說明你受到了瀏覽器同學的關懷:D)。

其實因果關系是這樣的:img.setAttribute('crossOrigin', 'anonymous');會讓request header加上Origin字段匣吊,從而變成了一個CORS請求

Paste_Image.png

(如果你想進一步了解CORS儒拂,可以看看阮老師的這篇文章。)

回到正題色鸳,既然問題是response header中不帶AC社痛,那讓服務端返回應該就可以了吧?

如果服務端真的沒有配置CORS命雀,那先讓他們配置好蒜哀。

但是?,即使配置了?吏砂,仍然可能存在?問題凡怎。

在我遇到的情況里校焦,其實服務端是做了配置的,那誰來背鍋统倒?

==================== 緩存 ====================

首先寨典,第一鍋要給瀏覽器緩存

這里先贅述一下:我們第一次訪問一個頁面時房匆,會發(fā)現(xiàn)圖片會慢慢加載出來耸成;當我們再次訪問同一個頁面時,會發(fā)現(xiàn)圖片很快就加載出來了浴鸿。主要就是因為瀏覽器第一次已經把圖片緩存下來了井氢,第二次不需要再從服務端請求,而直接從緩存里取岳链。

雖然方便了花竞,但這可能引發(fā)其它問題。上面提到過掸哑,原先的圖片預加載代碼有問題约急,簡化版如下:

var img;
for(var i in images){
  img = new Image();
  img.src = images[i].url;
}

注意,這段代碼沒帶img.setAttribute('crossOrigin', 'anonymous');苗分。其實本質上并不是因為沒帶這句才出的問題厌蔽,跟實際的場景有關

當時的場景是:圖片預加載先行摔癣;然后編譯第一個涂鴉板奴饮,之后選中其它的涂鴉板再編譯該涂鴉板;每個涂鴉板編譯的時候也會去發(fā)送圖片請求(CORS請求)择浊。

問題的現(xiàn)象是:第一個涂鴉板的圖片加載出來了戴卜,后面幾個都沒加載出來。

why?

對于第一張圖片琢岩,兩個請求(來自預加載和涂鴉板編譯)幾乎是同時發(fā)送的叉瘩;而其它幾張圖片,都是預加載在先粘捎,編譯在后薇缅。如此,在編譯其它幾個涂鴉板時攒磨,瀏覽器會直接取緩存里取圖片泳桦。

而我們預加載時發(fā)送的是普通請求,這意味著這些請求的response不會帶AC(不是必然的娩缰,取決于服務端怎么做):

普通請求.png
CORS請求.png

所以灸撰,當其它涂鴉板編譯時,發(fā)出的是CORS請求,拿到的卻是不帶AC的response浮毯,結果必然出錯完疫。

這里我得再強調一下,并不是普通請求的response就一定不帶AC债蓝,這個取決于服務端怎么處理壳鹤。比如像請求七牛公共空間的圖片,不管是普通請求還是CORS請求饰迹,都會帶AC芳誓。

知道原理之后解決問題就簡單了,先清清緩存啊鸭,然后加上crossOrigin

var img;
for(var i in images){
  img = new Image();
  img.setAttribute('crossOrigin', 'anonymous');
  img.src = images[i].url;
}

So,到此為止锹淌?No,我們有請第二位背鍋先生:CDN緩存赠制!

上面提到過赂摆,我們的圖片域名由源站改為了CDN。

先還原一下當時的場景:
有一位老師用涂鴉板批改作業(yè)钟些,當她保存的時候發(fā)現(xiàn)保存不了(這是另一個無關的問題烟号,不贅述),就請QA哥哥幫忙厘唾。QA哥哥打開控制臺......(省略一萬字)褥符,然后在一個新tab里打開了一張圖片龙誊。當他再回到原頁面時抚垃,一刷新,發(fā)現(xiàn)這張圖片沒了趟大。當時我就跪地上了鹤树。。逊朽。

我是束手無策了罕伯,于是找了CDN的gg們幫忙。他們說的確存在這種問題叽讳,正在修復中追他。。
在進一步講之前岛蚤,結合我的手殘圖邑狸,先普及幾個CDN相關的知識:


Paste_Image.png
  1. CDN會緩存response,源站不會涤妒。
  2. CDN接收到請求時单雾,如果沒有緩存,會將請求發(fā)送到源站,將結果回傳給請求端硅堆,并且緩存結果(response)屿储,簡稱回源。
  3. CDN是根據url進行緩存的渐逃,比如你請求一次http://a.b.c/1.jpg够掠,之后再請求相同的url,那你拿到的是緩存下來的response朴乖;如果你加了個參數比如http://a.b.c/1.jpg?100祖屏,這個時候就會回源,但是并不會破壞掉http://a.b.c/1.jpg對應的緩存买羞。
  4. 以上3點只是我們這邊的情況袁勺,也許有特殊性。

現(xiàn)在可以簡單理理畜普,這是個怎樣的問題:
老師的圖片本來?是可以加載到的期丰,并且在沒「打開圖片」之前,都是發(fā)送的CORS請求(在涂鴉板預加載和編譯時發(fā)送)吃挑,這些CORS請求的response早已在A節(jié)點緩存了下來钝荡。
而打開這張圖片,意味著一次普通請求舶衬,奇怪的是埠通,請求去到了B節(jié)點,而B節(jié)點尚未緩存逛犹,所以進行了回源端辱。
而刷新頁面后,請求雖然是CORS請求虽画,但是卻又走到了B節(jié)點舞蔽,結果就是:一個CORS請求?拿到一個普通請求的response,瀏覽器由于同源策略而報錯码撰。

(正常情況下渗柿,如果一開始去到A節(jié)點,那么應該一直都是去A節(jié)點脖岛。)

嗯朵栖,道理明白了。那除了等gg們修復問題柴梆,還有什么解決辦法嗎陨溅?

我猜你已經想到了:加隨機數。
最終的做法是在圖片onerror的時候帶隨機數(比如時間戳)重發(fā)請求轩性,大概就是:

function requestImg(src){
  var img = new Image();
  img.src = src;
  img.onerror = function(){
    var timeStamp = +new Date();
    requestImg(src+'?'+timeStamp);
  }
}

總結

總得來說声登,當你遇到這兩個問題的時候狠鸳,需要做兩件事:

  1. img.setAttribute('crossOrigin', 'anonymous');
  2. 圖片請求失敗時,帶隨機數重發(fā)請求悯嗓。

參考

http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html
http://www.ruanyifeng.com/blog/2016/04/cors.html
http://stackoverflow.com/questions/20424279/canvas-todataurl-securityerror
http://stackoverflow.com/questions/32039568/what-are-the-integrity-and-crossorigin-attribute
https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末件舵,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子脯厨,更是在濱河造成了極大的恐慌铅祸,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件合武,死亡現(xiàn)場離奇詭異临梗,居然都是意外死亡,警方通過查閱死者的電腦和手機稼跳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門盟庞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人汤善,你說我怎么就攤上這事什猖。” “怎么了红淡?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵不狮,是天一觀的道長。 經常有香客問我在旱,道長摇零,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任桶蝎,我火速辦了婚禮驻仅,結果婚禮上,老公的妹妹穿的比我還像新娘俊嗽。我一直安慰自己雾家,他們只是感情好铃彰,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布绍豁。 她就那樣靜靜地躺著,像睡著了一般牙捉。 火紅的嫁衣襯著肌膚如雪竹揍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天邪铲,我揣著相機與錄音芬位,去河邊找鬼。 笑死带到,一個胖子當著我的面吹牛昧碉,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼被饿,長吁一口氣:“原來是場噩夢啊……” “哼四康!你這毒婦竟也來了?” 一聲冷哼從身側響起狭握,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤闪金,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后论颅,有當地人在樹林里發(fā)現(xiàn)了一具尸體哎垦,經...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年恃疯,在試婚紗的時候發(fā)現(xiàn)自己被綠了漏设。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡今妄,死狀恐怖愿题,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情蛙奖,我是刑警寧澤潘酗,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站雁仲,受9級特大地震影響仔夺,放射性物質發(fā)生泄漏。R本人自食惡果不足惜攒砖,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一缸兔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吹艇,春花似錦惰蜜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鼻听,卻和暖如春财著,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背撑碴。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工撑教, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人醉拓。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓伟姐,卻偏偏與公主長得像收苏,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子愤兵,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理倒戏,服務發(fā)現(xiàn),斷路器恐似,智...
    卡卡羅2017閱讀 134,701評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,283評論 25 707
  • 0. 前言 前面有被用戶投訴 APP 流量消耗厲害: 于是乎考慮了流量方面的問題杜跷。暫時 APP 中涉及流量的幾個方...
    zyl06閱讀 24,036評論 5 62
  • 發(fā)現(xiàn) 關注 消息 iOS 第三方庫、插件矫夷、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,120評論 4 61
  • 大年三十已經過了双藕,在外奔波的人們大多已回到了故鄉(xiāng)淑趾。當他們坐上了返程的列車上,雖然起始站不同忧陪,但終點都是家扣泊,都是那個...
    小城卜一閱讀 334評論 2 4