MutliQRCode

PC端和移动端都可用的二维码识别

当前为 2023-05-28 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         MutliQRCode
// @namespace    https://greasyfork.org/zh-CN/users/1073349
// @version      0.3
// @description  PC端和移动端都可用的二维码识别
// @author       4ehex
// @match        *://*/*
// @icon         data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iaWNvbiIgc3R5bGU9IndpZHRoOiAxZW07aGVpZ2h0OiAxZW07dmVydGljYWwtYWxpZ246IG1pZGRsZTtmaWxsOiBjdXJyZW50Q29sb3I7b3ZlcmZsb3c6IGhpZGRlbjsiIHZpZXdCb3g9IjAgMCAxMDI0IDEwMjQiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBwLWlkPSI3NjA1Ij48cGF0aCBkPSJNMTg2LjAyNjY2NyAwaDY1MS45NDY2NjZRMTAyNCAwIDEwMjQgMTg2LjAyNjY2N3Y2NTEuOTQ2NjY2UTEwMjQgMTAyNCA4MzcuOTczMzMzIDEwMjRIMTg2LjAyNjY2N1EwIDEwMjQgMCA4MzcuOTczMzMzVjE4Ni4wMjY2NjdRMCAwIDE4Ni4wMjY2NjcgMHoiIGZpbGw9IiMzNjg5RjUiIHAtaWQ9Ijc2MDYiPjwvcGF0aD48cGF0aCBkPSJNMjEzLjMzMzMzMyAzMDQuNjRBOTAuODggOTAuODggMCAwIDEgMzA0LjIxMzMzMyAyMTMuMzMzMzMzaDkwLjg4djQyLjY2NjY2N0gzMDYuMzQ2NjY3YTQ1LjY1MzMzMyA0NS42NTMzMzMgMCAwIDAtNDYuNTA2NjY3IDQ4LjY0djkxLjczMzMzM0gyMTMuMzMzMzMzek0zOTYuOCA4MTAuNjY2NjY3SDMwNi4zNDY2NjdhOTAuNDUzMzMzIDkwLjQ1MzMzMyAwIDAgMS05MC44OC05MS4zMDY2Njd2LTkxLjczMzMzM2g0Ni41MDY2NjZ2OTEuNzMzMzMzQTQ1LjY1MzMzMyA0NS42NTMzMzMgMCAwIDAgMzA4LjA1MzMzMyA3NjhoOTAuODhhMjk4LjY2NjY2NyAyOTguNjY2NjY3IDAgMCAwLTIuMTMzMzMzIDQyLjY2NjY2N3pNODEwLjY2NjY2NyA3MTkuMzZBOTAuNDUzMzMzIDkwLjQ1MzMzMyAwIDAgMSA3MTcuNjUzMzMzIDgxMC42NjY2NjdINjI3LjJ2LTQ2LjkzMzMzNGg5MC40NTMzMzNhNDUuNjUzMzMzIDQ1LjY1MzMzMyAwIDAgMCA0Ni41MDY2NjctNDYuNTA2NjY2di05MS4zMDY2NjdIODEwLjY2NjY2N3pNMjEzLjMzMzMzMyA0ODcuNjhoNTk1LjJ2NDYuOTMzMzMzSDIxMy4zMzMzMzN6TTgxMC42NjY2NjcgMzk2LjM3MzMzM2gtNDguNjRWMzA0LjY0YTQ1LjY1MzMzMyA0NS42NTMzMzMgMCAwIDAtNDYuMDgtNDYuNTA2NjY3aC05MC44OFYyMTMuMzMzMzMzaDkwLjg4YTkwLjg4IDkwLjg4IDAgMCAxIDkwLjg4IDkxLjMwNjY2N3oiIGZpbGw9IiNGRkZGRkYiIHAtaWQ9Ijc2MDciPjwvcGF0aD48L3N2Zz4=
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/jsQR.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/html2canvas.min.js
// @grant        unsafeWindow
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    //全局变量 被选中的图片元素
    var g_img_ele = null;
    var g_is_moblie_env = isPhone();

    //适配移动端 很多移动端浏览器并没有实现油猴接口
    //添加'识别二维码'按钮样式
    let btn_style = document.createElement('style');
    btn_style.type = 'text/css';
    if (!g_is_moblie_env){
        btn_style.innerText = `.idtfy_div{width:5.5vw;height:2.1vh;font-size:1vh;color:#000000;background:#F8F8FF;border-radius:1.5vh;border:0.12vh solid #f2cac9;line-height:2vh;text-align:center;vertical-align:middle;z-index:99999999;display:none;position:absolute;top:20;left:20;cursor:pointer;box-shadow:0.13vh 0.13vh 0.1vh #888888;}`;
        //GM_addStyle(`.idtfy_div{width:5.5vw;height:2.1vh;font-size:1vh;color:#000000;background:#F8F8FF;border-radius:1.5vh;border:0.12vh solid #f2cac9;line-height:2vh;text-align:center;vertical-align:middle;z-index:99999999;display:none;position:absolute;top:20;left:20;cursor:pointer;box-shadow:0.13vh 0.13vh 0.1vh #888888;}`);
    }
    else{
        btn_style.innerText = `.idtfy_div{width:25vw;height:3vh;font-size:1.6vh;color:#000000;background:#F8F8FF;border-radius:1.5vh;border:0.14vw solid #f2cac9;line-height:3vh;text-align:center;vertical-align:middle;z-index:99999999;display:none;position:absolute;top:20;left:20;cursor:pointer;box-shadow:0.15vw 0.15vw 0.13vw #888888;}`;
        //GM_addStyle(`.idtfy_div{width:25vw;height:3vh;font-size:1.6vh;color:#000000;background:#F8F8FF;border-radius:1.5vh;border:0.14vw solid #f2cac9;line-height:3vh;text-align:center;vertical-align:middle;z-index:99999999;display:none;position:absolute;top:20;left:20;cursor:pointer;box-shadow:0.15vw 0.15vw 0.13vw #888888;}`);
    }
    document.head.appendChild(btn_style);

    //添加'识别二维码'按钮
    var identify_div = document.createElement('div');
    identify_div.id = 'identify_div';
    identify_div.className = 'idtfy_div';//👇添加一个图标
    identify_div.innerHTML = `<svg class="icon" style="width:1.3vh;height:1.3vh;vertical-align:middle;fill:currentColor;overflow:hidden" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3325"><path d="M613.888 715.264h-41.984C448.512 715.264 348.16 614.912 348.16 491.52s99.84-223.232 223.232-223.232h41.984c123.392 0 223.232 99.84 223.232 223.232 0.512 123.392-99.328 223.744-222.72 223.744z" fill="#CAD3FF" opacity=".2" p-id="3326"></path><path d="M873.984 940.032h-152.576c-14.336 0-25.6-11.264-25.6-25.6s11.264-25.6 25.6-25.6h147.456v-147.456c0-14.336 11.264-25.6 25.6-25.6s25.6 11.264 25.6 25.6v152.576c0 25.088-20.48 46.08-46.08 46.08zM894.464 311.296c-14.336 0-25.6-11.264-25.6-25.6v-148.48h-147.456c-14.336 0-25.6-11.264-25.6-25.6s11.264-25.6 25.6-25.6h152.576c25.6 0 46.08 20.48 46.08 46.08v153.6c0 13.824-11.264 25.6-25.6 25.6z m-20.48-174.08zM131.072 311.296c-14.336 0-25.6-11.264-25.6-25.6v-153.6c0-25.6 20.48-46.08 46.08-46.08h152.576c14.336 0 25.6 11.264 25.6 25.6s-11.264 25.6-25.6 25.6H156.672v148.48c0 13.824-11.776 25.6-25.6 25.6zM304.128 940.032H151.552c-25.6 0-46.08-20.48-46.08-46.08v-152.576c0-14.336 11.264-25.6 25.6-25.6s25.6 11.264 25.6 25.6v147.456h147.456c14.336 0 25.6 11.264 25.6 25.6s-11.264 25.6-25.6 25.6zM726.528 537.6H297.472c-14.336 0-25.6-11.264-25.6-25.6s11.264-25.6 25.6-25.6h429.056c14.336 0 25.6 11.264 25.6 25.6s-11.776 25.6-25.6 25.6z" fill="#4E63DD" p-id="3327"></path></svg> 识别二维码`;
    identify_div.onclick = OnClickedIdentifyBtn;
    document.body.appendChild(identify_div);

    //添加精简后的'notie.js' 一款纯js实现的消息弹窗 github: https://github.com/jaredreich/notie
    const notie_script = document.createElement('script')
    notie_script.innerText = `var notie=function(){function D(a,b,c){document.activeElement.blur(),C++,setTimeout(function(){C--},1e3*e+10),1==C&&(z?(clearTimeout(A),clearTimeout(B),F(function(){E(a,b,c)})):E(a,b,c))}function E(b,c,d){var f;switch(z=!0,f=0,f="undefined"==typeof d?3e3:1>d?1e3:1e3*d,b){case 1:v.style.backgroundColor=g,v.onclick=function(){};break;case 2:v.style.backgroundColor=h,v.onclick=alert_outer_onclick;break;case 3:v.style.backgroundColor=i,v.onclick=alert_outer_onclick;break;case 4:v.style.backgroundColor=j,v.onclick=alert_outer_onclick}x.innerHTML=c,v.style.top="-10000px",v.style.display="table",v.style.top="-"+v.offsetHeight-5+"px",A=setTimeout(function(){a&&(v.style.boxShadow="0px 0px 10px 0px rgba(0,0,0,0.5)"),v.style.MozTransition="all "+e+"s ease",v.style.WebkitTransition="all "+e+"s ease",v.style.transition="all "+e+"s ease",v.style.top=0,B=setTimeout(function(){F(function(){})},f)},20)}function F(b){v.style.top="-"+v.offsetHeight-5+"px",setTimeout(function(){a&&(v.style.boxShadow=""),v.style.MozTransition="",v.style.WebkitTransition="",v.style.transition="",v.style.top="-10000px",z=!1,b&&b()},1e3*e+10)}var v,w,x,z,A,B,C,a=!0,b="18px",c="24px",d=600,e=.3,g="#57BF57",h="#E3B771",i="#E1715B",j="#4D82D6",k="#FFF",l="notie-alert-outer",m="notie-alert-inner",n="notie-alert-text",o=function(a){a.style.fontSize=window.innerWidth<=d?b:c},p=500,q=function(a,b,c){var d;return function(){var e=this,f=arguments,g=function(){d=null,c||a.apply(e,f)},h=c&&!d;clearTimeout(d),d=setTimeout(g,b),h&&a.apply(e,f)}};return window.addEventListener("keydown",function(a){var b=13==a.which||13==a.keyCode,c=27==a.which||27==a.keyCode;z&&(b||c)&&(clearTimeout(A),clearTimeout(B),F())}),"undefined"==typeof Element.prototype.addEventListener&&(Element.prototype.addEventListener=Window.prototype.addEventListener=function(a,b){return a="on"+a,this.attachEvent(a,b)}),v=document.createElement("div"),v.id=l,v.style.position="fixed",v.style.top="0",v.style.left="0",v.style.zIndex="999999999",v.style.height="auto",v.style.width="100%",v.style.display="none",v.style.textAlign="center",v.style.cursor="default",v.style.MozTransition="",v.style.WebkitTransition="",v.style.transition="",v.style.cursor="pointer",alert_outer_onclick=function(){clearTimeout(A),clearTimeout(B),F()},w=document.createElement("div"),w.id=m,w.style.padding="20px",w.style.display="table-cell",w.style.verticalAlign="middle",v.appendChild(w),x=document.createElement("span"),x.id=n,x.style.color=k,x.style.fontSize=window.innerWidth<=d?b:c,window.addEventListener("resize",q(o.bind(null,x),p),!0),w.appendChild(x),document.body.appendChild(v),z=!1,C=0,{alert:D,alert_hide:F}}();`;
    document.head.appendChild(notie_script);
    /*
    GM_addElement('script', {
        textContent: `var notie=function(){function D(a,b,c){document.activeElement.blur(),C++,setTimeout(function(){C--},1e3*e+10),1==C&&(z?(clearTimeout(A),clearTimeout(B),F(function(){E(a,b,c)})):E(a,b,c))}function E(b,c,d){var f;switch(z=!0,f=0,f="undefined"==typeof d?3e3:1>d?1e3:1e3*d,b){case 1:v.style.backgroundColor=g,v.onclick=function(){};break;case 2:v.style.backgroundColor=h,v.onclick=alert_outer_onclick;break;case 3:v.style.backgroundColor=i,v.onclick=alert_outer_onclick;break;case 4:v.style.backgroundColor=j,v.onclick=alert_outer_onclick}x.innerHTML=c,v.style.top="-10000px",v.style.display="table",v.style.top="-"+v.offsetHeight-5+"px",A=setTimeout(function(){a&&(v.style.boxShadow="0px 0px 10px 0px rgba(0,0,0,0.5)"),v.style.MozTransition="all "+e+"s ease",v.style.WebkitTransition="all "+e+"s ease",v.style.transition="all "+e+"s ease",v.style.top=0,B=setTimeout(function(){F(function(){})},f)},20)}function F(b){v.style.top="-"+v.offsetHeight-5+"px",setTimeout(function(){a&&(v.style.boxShadow=""),v.style.MozTransition="",v.style.WebkitTransition="",v.style.transition="",v.style.top="-10000px",z=!1,b&&b()},1e3*e+10)}var v,w,x,z,A,B,C,a=!0,b="18px",c="24px",d=600,e=.3,g="#57BF57",h="#E3B771",i="#E1715B",j="#4D82D6",k="#FFF",l="notie-alert-outer",m="notie-alert-inner",n="notie-alert-text",o=function(a){a.style.fontSize=window.innerWidth<=d?b:c},p=500,q=function(a,b,c){var d;return function(){var e=this,f=arguments,g=function(){d=null,c||a.apply(e,f)},h=c&&!d;clearTimeout(d),d=setTimeout(g,b),h&&a.apply(e,f)}};return window.addEventListener("keydown",function(a){var b=13==a.which||13==a.keyCode,c=27==a.which||27==a.keyCode;z&&(b||c)&&(clearTimeout(A),clearTimeout(B),F())}),"undefined"==typeof Element.prototype.addEventListener&&(Element.prototype.addEventListener=Window.prototype.addEventListener=function(a,b){return a="on"+a,this.attachEvent(a,b)}),v=document.createElement("div"),v.id=l,v.style.position="fixed",v.style.top="0",v.style.left="0",v.style.zIndex="999999999",v.style.height="auto",v.style.width="100%",v.style.display="none",v.style.textAlign="center",v.style.cursor="default",v.style.MozTransition="",v.style.WebkitTransition="",v.style.transition="",v.style.cursor="pointer",alert_outer_onclick=function(){clearTimeout(A),clearTimeout(B),F()},w=document.createElement("div"),w.id=m,w.style.padding="20px",w.style.display="table-cell",w.style.verticalAlign="middle",v.appendChild(w),x=document.createElement("span"),x.id=n,x.style.color=k,x.style.fontSize=window.innerWidth<=d?b:c,window.addEventListener("resize",q(o.bind(null,x),p),!0),w.appendChild(x),document.body.appendChild(v),z=!1,C=0,{alert:D,alert_hide:F}}();`
    });
    */

    if (!g_is_moblie_env){
        //电脑端 右键弹出 双击隐藏
        window.onmousedown = function(e) {
            if (e.button == 2) {//右键
                var clickedElement = e.target;
                if ((g_img_ele = GetQREle(clickedElement)) != null){
                    document.getElementById("identify_div").style.top = (e.pageY + 10) + "px";
                    document.getElementById("identify_div").style.left = (e.pageX) + "px";
                    document.getElementById("identify_div").style.display = 'block';
                }
            }
        };

        window.ondblclick = function(e) {//双击隐藏按钮
            document.getElementById("identify_div").style.display = 'none';
        }
    }
    else{
        //移动端 监听长按事件
        let time_out = 0, touch_time = 800;
        window.addEventListener("touchstart", function(e){
            time_out = setTimeout(function(){
                var touchedElement = e.target;
                if ((g_img_ele = GetQREle(touchedElement)) != null) {
                    document.getElementById("identify_div").style.top = (e.touches[0].pageY - 50) + "px";
                    document.getElementById("identify_div").style.left = (e.touches[0].pageX + 20) + "px";
                    document.getElementById("identify_div").style.display = 'block';
                }
            }, touch_time);//touch_time毫秒后弹出识别按钮

            //触碰其他地方则隐藏按钮
            if (e.target.id != 'identify_div'){
                g_img_ele = null;
                document.getElementById("identify_div").style.display = 'none';
            }
        });
        window.addEventListener("touchmove", function(e){
            // 如果触摸未达到 touch_time ms且开始移动,则清除计时器
            clearTimeout(time_out);
            time_out = 0;
        });
        window.addEventListener("touchend", function(e){
            // 如果触摸未达到 touch_time ms且离开屏幕,则清除计时器
            clearTimeout(time_out);
            time_out = 0;
        });
    }

    //通过UA判断是否是移动端
    function isPhone() {
        var info = navigator.userAgent;
        var isPhone = /mobile/i.test(info);
        return isPhone;
    }

    //从传入的元素中获取存有图片的元素 若没有则返回null
    function GetQREle(ele){
        let ret_ele = null;

        if (ele.tagName == 'IMG'){
            if (ele.src != null) ret_ele = ele;
        }
        else{
            //遍历子元素看看是否有图片
            var childs = ele.childNodes;
            if (childs.length >= 6) return ret_ele;
            for(var i = childs.length - 1; i >= 0; i--) {
                if (childs[i].tagName == 'IMG' && childs[i].src != null){
                    ret_ele = childs[i];
                    break;
                }
            }
        }

        return ret_ele;
    }

    //通过html2canvs截图并识别
    function Html2CanvasAndIndentify(ele){
        html2canvas(ele,{
            useCORS: true, //开启跨域配置
            allowTaint: false, //允许跨域图片
        }).then(function(canvas) {
            var imgData = canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height);

            const code = jsQR(imgData.data, imgData.width, imgData.height, {
                inversionAttempts: "dontInvert",
            });
            if (code == null) {
                notie.alert(3, '识别失败!', 3);
            }else {
                let display_text = code.data, ope_html = '<div><a id="copy_btn" href="javascript:void(0);">复制</a>[placeholder_]<a style="margin-left: 20px" href="javascript:void(0);" onclick="notie.alert_hide()">关闭</a></div>';
                if (g_is_moblie_env && (code.data.length >= 40)){//如果是移动端 且识别内容过长 则隐藏一部分内容
                        display_text = code.data.substr(0, 35) + '...';
                }
                if (isValidURL(code.data)){
                    ope_html = ope_html.replace("[placeholder_]", '<a id="goto_btn" href="' + code.data + '" target="_blank" style="margin-left: 20px">转到</a>');
                }
                else{
                    ope_html = ope_html.replace("[placeholder_]", '<a id="search_btn" href="https://www.baidu.com/s?wd=' + encodeURIComponent(code.data) + '" target="_blank" style="margin-left: 20px">搜索</a>');
                }
                notie.alert(1, '<a id="all_text" style="display: none;">'+ code.data +'</a>识别到以下文本:<br><div style="word-wrap:break-word;">' + display_text + '</div>' + ope_html, 20);
                document.getElementById("copy_btn").onclick = OnClickedCopyBtn;
            }

        }).catch(function(err){
            notie.alert(3, 'html2canvas出错:<br>' + err, 5);
        });
    }


    //'识别二维码'按钮事件
    function OnClickedIdentifyBtn(){
        if ((typeof jsQR != 'function') || (typeof html2canvas != 'function')){
            notie.alert(3, '外部JS脚本未加载成功!', 3);
            return;
        }

        if (g_img_ele == null){
            notie.alert(2, '未选中任何图片', 2);
        }
        else {
            Html2CanvasAndIndentify(g_img_ele);
        }
        document.getElementById("identify_div").style.display = 'none';
    }

    //'复制'按钮单击事件
    function OnClickedCopyBtn(){
        //GM_setClipboard(document.getElementById("all_text").innerText);
        navigator.clipboard.writeText(document.getElementById("all_text").innerText)
        notie.alert_hide();
        notie.alert(1, '复制成功!', 2);
    }

    //判字符串是否为URL
    function isValidURL(str) {
        var strRegex = "^((https|http|ftp)?://)"
        + "?(([0-9a-z_!~*'().&=+$%-]+: )?[0-9a-z_!~*'().&=+$%-]+@)?" //ftp的user@
        + "(([0-9]{1,3}.){3}[0-9]{1,3}" // IP形式的URL- 199.194.52.184
        + "|" // 允许IP和DOMAIN(域名)
        + "([0-9a-z_!~*'()-]+.)*" // 域名- www.
        + "([0-9a-z][0-9a-z-]{0,61})?[0-9a-z]." // 二级域名
        + "[a-z]{2,6})" // first level domain- .com or .museum
        + "(:[0-9]{1,4})?" // 端口- :80
        + "((/?)|" // a slash isn't required if there is no file name
        + "(/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+/?)$";
        var re=new RegExp(strRegex);
        return re.test(str);
    }
})();