最近在實現一個功能,需求以下:html
前提:當前頁面無彈窗jquery
頁面任意位置執行粘貼angularjs
讀取剪切板中的截屏數據chrome
上傳截圖segmentfault
首先仍是從網上找相關的例子。api
找到了SF上的專欄文章《js獲取剪切板內容,js控制圖片粘貼》。app
因而基於這個,作出了初版的截圖上傳功能。jsp
因爲項目使用的是angularjs,事先已經封裝好一套上傳圖片的辦法,只須要調用 $scope.image = blob
,自動就會發送、上傳該文件。wordpress
我是半路介入項目的。原來爲數很少的幾個js文件實在太大,一個apiService.js
就累積了三四千行,各類服務都在這個文件裏,主視圖就一個mainController,也是三四千行。測試
說實話,我真的驚呆了。
因此仍是儘可能避免修改原來的代碼。
按照我本身習慣,把功能封裝成directive
,獨立建一個文件。
代碼以下:(特別鳴謝本期節目的文章)
/** * @description: 截屏上傳 * @author: angusfu1126@qq.com * @date: 2016-03-03 20:59:09 */ app.directive('screenshotOrDragUpload', /*ngInject*/ function($filter) { return { restrict: 'A' link: function($scope, iElm, iAttrs, controller) { var imageRegex = /^image\//i; // 粘貼截圖事件 document.addEventListener('paste', onPasteHandler, false); // 做用域銷燬的時候解除事件綁定 $scope.$on('$destroy', function() { document.removeEventListener('paste', onPasteHandler); }); /** * 全局蒙版顯示的時候 * 不執行粘貼或者拖拽功能 * 避免和各類彈層ng-show條件太耦合 * 此處使用DOM方法判斷 */ function isMaskShown() { // 項目依賴於jquery return angular.element('.global-mask').is(':visible'); } /** * 根據時間戳命名 */ function generateFileName(user) { return $filter('date')(new Date(), 'yyyyMMdd_HH:MM:ss'); } /** * 處理 `ctrl + v` 截圖粘貼事件 */ function onPasteHandler(e) { if (isMaskShown()) return; var clipboardData = e.clipboardData; var ua = window.navigator.userAgent; // 若是沒法獲取剪貼板則返回 if (!clipboardData || !clipboardData.items) { return; } // Mac平臺下Chrome49版本如下 // 複製Finder中的文件的Bug Hack掉 // see: https://segmentfault.com/a/1190000004288686 if (clipboardData.items && clipboardData.items.length === 2 && clipboardData.items[0].kind === "string" && clipboardData.items[1].kind === "file" && clipboardData.types && clipboardData.types.length === 2 && clipboardData.types[0] === "text/plain" && clipboardData.types[1] === "Files" && ua.match(/Macintosh/i) && Number(ua.match(/Chrome\/(\d{2})/i)[1]) < 49 ) { return; } var len = clipboardData.items.length, item = null, blob = null; while (len--) { item = clipboardData.items[len]; if (item.kind == "file") { blob = item.getAsFile(); if (imageRegex.test(blob.type) && blob.size > 0) { blob.name = generateFileName(); // 調用上傳 $scope.image = blob; break; } } } } } }; });
固然,文章不可能就此結束。。。
分割線休息片刻
==============================================================
上述功能只有在Chrome和Safari中有效,但到火狐上面就掛掉了啊。。。
測試一下,給document綁定paste事件,粘貼的時候壓根就讀不到數據。
火狐下面,並無clipboardData.items
這一項。
o(╯□╰)o
那怎麼辦呢?
只能退而求其次。放棄,或者尋求降級的辦法。
就在我以爲無路可走的時候,火狐的一個特色讓我眼前一亮。。。
分別用chrome和firefox打開這個demo試試看,試着用qq截個圖或者在文件夾中複製一張圖片,粘貼在紅色框框裏。
有沒有發現,只有在火狐下能把圖粘貼進來?
嗯,解決辦法就在這裏了。
其實,demo中的紅色框框是一個有contenteditable
屬性的div
。
關於contenteditable
,此處有張鑫旭大神的博文兩篇,且記在此處備忘:
firefox下面,是能夠把剪切板中的圖片數據粘貼進去的,而chrome下面則不行了。
而項目的輸入框,正好是一個pre
標籤加上contenteditable
屬性模擬出來的。完美~~~(此處應有金星老師表情包)
好了,在火狐中粘貼截圖以後,右鍵查看一下,是否是像下圖醬紫的?
有木有看到醒目的img
標籤?
有木有看到醒目的data:image/png;base64,
?
辦法有了。解決方案以下:
監聽keydown
事件
檢測輸入框是否爲空
非空:不容許粘貼圖片(但咱們不能事先判斷數據類型,只能迅速remove掉img元素)
空的:獲取img元素及其src數據,而後迅速移除元素
固然,此處是有坑的。。。
具體坑在哪裏呢?看代碼吧。其實我以爲我可能沒徹底解決。
if (/firefox/i.test(navigator.userAgent)) { var URL = (window.URL || window.mozURL), supportTransform = URL && window.Blob && window.atob && window.ArrayBuffer && window.Uint8Array, // see http://jsperf.com/blob-base64-conversion convertBase64UrlToBlob = function(urlData) { //去掉url的頭,並轉換爲byte var bytes = window.atob(urlData.split(',')[1]); //處理異常,將ascii碼小於0的轉換爲大於0 var ab = new ArrayBuffer(bytes.length); var ia = new Uint8Array(ab); for (var i = 0; i < bytes.length; i++) { ia[i] = bytes.charCodeAt(i); } return new Blob([ab], { type: 'image/png' }); }; $('pre').on('keydown', function(e) { var isCtrlV = (e.ctrlKey && e.keyCode == '86'); if (!supportTransform || !isCtrlV) return; var $this = $(this), html = $this.html(), canPasteImage = false; // Notice // 火狐的坑在這裏啊啊啊啊 // 只有空的時候才能粘貼圖片 if (!html || html === '<br>') { canPasteImage = true; } setTimeout(function() { var $imgs = $this.find('img').remove(), data = $imgs.eq(0).attr('src'); if (canPasteImage && data) { var blob = convertBase64UrlToBlob(data); blob.name = generateFileName(); // 調用上傳 $scope.image = blob; } }, 0); }); }
作個筆記: Blob對象和base64字符串的轉換, http://jsperf.com/blob-base64-conversion
目前還沒在IE上測試過,不知道結果如何。