【在网页中获取截图数据】Chrome和Firefox下的实战经验

【转载自我在segmentfault的专栏:https://segmentfault.com/a/1190000004584071】

最近在实现一个功能,需求如下:

  • 前提:当前页面无弹窗
  • 页面任意位置执行粘贴
  • 读取剪切板中的截屏数据
  • 上传截图

首先还是从网上找相关的例子。

找到了SF上的专栏文章《js获取剪切板内容,js控制图片粘贴》。

于是基于这个,做出了第一版的截图上传功能。

由于项目使用的是angularjs,事先已经封装好一套上传图片的办法,只需要调用 $scope.image = blob,自动就会发送、上传该文件。

我是半路介入项目的。原来为数不多的几个js文件实在太大,一个apiService.js就累积了三四千行,各种服务都在这个文件里,主视图就一个mainController,也是三四千行。

说实话,我真的惊呆了。

所以还是尽量避免修改原来的代码。

按照我自己习惯,把功能封装成directive,独立建一个文件。

代码如下:(特别鸣谢本期节目的文章)

/**
 * @description: 截屏上传
 * @author:      [email protected]
 * @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上测试过,不知道结果如何。

以上。

谢谢阅读。

如蒙您收藏、推荐一下,那自然是极好的了。

时间: 2024-08-09 15:14:18

【在网页中获取截图数据】Chrome和Firefox下的实战经验的相关文章

网页中抓取数据

下面写个例子,实现从网页中抓取数据. 这个例子中,只是从网页中获取了数据,但是没有进行任何处理,只是将数据保存到一个txt文件中. 该例子是在android工程中写的. package com.example.creepertest; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.I

使用Xpath从网页中获取数据

/// <summary> /// 从官方网站中抓取产品信息存放在本地数据库中 /// </summary> /// <returns></returns> public List<ProductMessage> GetlistProductMessage() { string html = GetProductsDescriptionsImage("http://www.grandcanyononepoint.com/products

浅谈如何使用python抓取网页中的动态数据

我们经常会发现网页中的许多数据并不是写死在HTML中的,而是通过js动态载入的.所以也就引出了什么是动态数据的概念, 动态数据在这里指的是网页中由Javascript动态生成的页面内容,是在页面加载到浏览器后动态生成的,而之前并没有的. 在编写爬虫进行网页数据抓取的时候,经常会遇到这种需要动态加载数据的HTML网页,如果还是直接从网页上抓取那么将无法获得任何数据. 今天,我们就在这里简单聊一聊如何用python来抓取页面中的JS动态加载的数据. 给出一个网页:豆瓣电影排行榜,其中的所有电影信息都

IOS从视频中获取截图

从视频中获取截图: NSString *movpath =[[NSBundle mainBundle] pathForResource:@”iosxcode4″ ofType:@”mov”]; mpviemController =[[MPMoviePlayerViewController alloc] initWithContentURL:[NSURL fileURLWithPath:movpath]]; MPMoviePlayerController *mp=[mpviemController

如何从Zabbix数据库中获取监控数据

做过Zabbix的同学都知道,Zabbix通过专用的Agent或者SNMP收集相关的监控数据,然后存储到数据库里面实时在前台展示.Zabbix监控数据主要分为以下两类: 历史数据:history相关表,从history_uint表里面可以查询到设备监控项目的最大,最小和平均值,即存储监控数据的原始数据. 趋势数据:trends相关表,趋势数据是经过Zabbix计算的数据,数据是从history_uint里面汇总的,从trends_uint可以查看到监控数据每小时最大,最小和平均值流量. Zabb

Android中获取网络数据时的分页加载

//此实在Fragment中实现的,黄色部分为自动加载,红色部分是需要注意的和手动加载,    蓝色部分是睡眠时间,自我感觉不用写  ,还有就是手动加载时,不知道为什么进去后显示的就是最后一行,求大神指教 public class Fragment1 extends Fragment{               //加载的第几页        private int index = 0;            private List<News> news=new ArrayList<

Django将从数据库中获取到数据转换为dict

这种方式只能应用于从数据库中获取到的单条数据,例如models.Users.objects.get()获取到的数据 from django.forms.models import model_to_dict class Index(VIew): def get(self, request): userObj = models.Users.objects.get(id = 1) userDict = model_to_dict(userObj) print(userDict) return Htt

android开发中获取&lt;meta-data&gt;数据

在 AndroidManifest.xml 中,<meta-data>元素是一个键值对,往往被包含在<application> .<activity>.<service>和<receiver>等元素中,但是不同父元素中<meta-data>读取方法也不同. <meta-data>基本结构:<meta-data android:name="string" android:resource="

在链表中获取一个数据、查找操作C语言实现

SN *Get_S_Node ( SN *head ) { /* head 为要查询的链表的头指针 */ SN *Get_S_Node = NULL; INT32 OSM = 1,i32i = 0, data_num = 0; /* OSM是标志符,i32i是一个循环体内的变量,data为要获取的元素的序号 */ Get_S_Node = ( SN * )malloc( sizeof (SN) ); Get_S_Node = head; /* 输入要获取的数据 */ OSM = OSM_Prin