【在網頁中獲取截圖數據】Chrome和Firefox下的實戰經驗

最近在實現一個功能,需求以下: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上測試過,不知道結果如何。

相關文章
相關標籤/搜索