Greasy Fork

Greasy Fork is available in English.

链接预览

鼠标指向链接标识图标预览链接网页

当前为 2024-01-12 提交的版本,查看 最新版本

// ==UserScript==
// @name         链接预览
// @name:zh-cn   链接预览
// @name:en      Link Previewer
// @namespace    http://greasyfork.icu/zh-CN/users/1073-hzhbest
// @version      1.4
// @description  鼠标指向链接标识图标预览链接网页
// @description:zh-cn  鼠标指向链接标识图标预览链接网页
// @description:en Hovering to preview a link
// @author       hzhbest
// @match        http*://*/*
// @grant        none
// @license      MIT
// @run-at       document-end
// ==/UserScript==

// 1.4:增加小链接的延时,改进定位算法,应对视频网站链接,增加钉住按钮,智能决定预览窗位置
// todo:

(function () {
    'use strict';

    // --------自定义区-------- //
    const minHgap = 50, minVgap = 80;        // 预览窗距窗口边缘的右、下最小距离:像素
    var winWidth = 700, winHeight = 550;     // 预览窗宽、高:像素
    const scale = 0.9;                       // 预览窗内页面放大率:(0~1]
    const animationtime = 0.5;               // 动画时长:秒
    const iconsize = 20;                     // 图标放大后大小:像素
    const icontrpr = 0.7;                    // 图标放大后不透明度:(0~1)
    const pre = "░░░░░░ ";                   // 移动手柄显示:字符串
    const delaysec = 1;                      // 触发预览等候延时:秒
    const moredelaysec = 1;                  // 对超小链接增加等候延时:秒
    var pinned = false;                      // 初始钉住状态:Boolean
    const preID = "__link__prev_win"         // 预览窗id
    const urlplaceArr = [                    // 替换链接网址数组,{命名,网址正则,关键编号正则,替换网址模板}
        {
            name: "B站",
            scrurl: /bilibili\.com\//,
            coreID: /BV[^&\/]+/,
            desturl: "https://www.bilibili.com/blackboard/html5mobileplayer.html?bvid=--coreID--&autoplay=1"
        },
        /* {
            name: "油管",
            scrurl: /youtube\.com\//,
            coreID: /(?<=(watch\?v=|\/shorts\/))[^&\/]+/,
            desturl: "https://www.youtube.com/embed/--coreID--?si=Kjs9BPxNAwVzGPxm"
        } */
        {
            name: "西瓜",
            scrurl: /ixigua\.com\//,
            coreID: /(?<=\/)\d+\?/,
            desturl: "https://www.ixigua.com/iframe/--coreID--autoplay=1"
        }
    ];
    // --------自定义区结束--------//

    const ualen = urlplaceArr.length;
    const ixonimg = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAoUlEQVQ4y8XSyw3CMBAE0AekkHTBBZGkCa40QA9AOVADEkGiEJfCxZEsk+8F5mLZs7Mzay3/xmpm3Tm7v9DCeqHhDhdU3UORkHVKJAi4osEJj5RME1SxiZ64De444J2SRVbcRjdZslsUP7Efa6BH3DkHHHOD9UxxG8VlXjTUoExit2MRh0YI2MZzFGMjTIr7EtQTf/I1zmbBWoe4E7OS/Q4fA1sbIN+Dzp4AAAAASUVORK5CYIIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==";
    const inonimg = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAATr4AAE6+AGeOBvEAAAA3klEQVQ4y83SPUpDURAF4M8YooUiIi7BQBaQ2lqirZjnVrIFCwsLDbbZgLoAO8FC7EJcgNgZUpvYTOByee/513hguDB3zpm5cy5/xOovOI0qgUZJ5FjDBbbxnF5s4hHjLPYz8jVmOFwmm0n3PdzhJXIreM06H6OP23y0LbyjVzJ2C8O8c51AGwOsB/kqyEd1m00FephiF5dfkZs1Vp0H8RQ3kS/iHJV6mmADJziLRe4kAkXlpwgsMI8Y4AEHP3nCPTohtMTbdwUKdCtq5+HQpEzgA09R0K5xa5p8tH+CT7aEK2vlnm2YAAAAAElFTkSuQmCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==";
    const loadimg = "data:image/png;base64,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";
    const domainregex = /(?<=:\/\/)[^\/]+/;
    const id = "__link__prev";
    const css = `
        /* 链接样式 */
        a.__link__preved {
            outline: 3px solid #3e3ed3;
        }
        a.___prevlink.__pr {
            position: relative;
        }
        /* 图标样式 */
        a.___prevlink>img.___previcon {
            width: 16px !important; height: 16px !important;
            opacity: 0%; transition: opacity 0.5s ease-out, background 0s;
            position: absolute; pointer-events: auto !important;
            display: inline-block !important; margin: 0 !important; 
        }
        a.___prevlink>img.___previcon:hover {
            background: #3e3ed3 !important; transition: background ${delaysec}s !important;
        }
        a.___prevlink>img.___previcon.__moredelay:hover {
            transition: background ${delaysec + moredelaysec}s !important;
        }
        *:hover>a.___prevlink>img.___previcon {
            opacity: 30%;
        }
        a.___prevlink:hover>img.___previcon {
            opacity: ${icontrpr}; background: white; border: 1px solid #8d8d8db1;
            transition: opacity 0.5s linear; z-index: 10008;
            transform: scale(${iconsize / 16});
        }
        /* 预览窗样式 */
        div#${preID} {
            opacity: 0; position: absolute; z-index: 10009; box-shadow: 1px 1px 7px 1px #717171;
            padding: 0; margin: 0; background-color: white; transition: ${animationtime}s ease;
        }
        div#${preID}.__close {
            transition: 0s !important; display: none;
        } 
        div#${preID}.__loading {
            background: url(${loadimg}) no-repeat center center; background-color: #e7e7e7;
            box-shadow: 1px 1px 7px 1px #dba87c !important;
        } 
        div#${preID}.__visible {
            opacity: 1; transition: 0.2s !important;
        }
        div#${preID}.visible .__link__prev_ifr {
            border: none; transform: scale(${scale});
            max-width: unset !important; width: calc(100% / ${scale});
            max-height: unset !important; height: calc(100% / ${scale});
        }
        div#${preID}.__onmove, div#${preID}.__onsize, div#${preID}.__onpin{
            transition: 0s !important;
        }
        div#${preID}.__onmove {
            /* opacity: 0.7; */
        }
        div#${preID}.__pinned {
            box-shadow: 1px 1px 7px 1px #a36835; position: fixed; transition: 0s !important;
        }
        /* 关闭按钮样式 */
        @keyframes rotating_button {
            from {
                transform: rotate(0deg);
            }
            to {
                transform: rotate(360deg);
            }
        }
        div#${preID}>.__link__clos_btn {
            position: absolute; top: -10px; right: -10px; z-index: 10010;
            line-height: 24px; width: 24px; height: 24px;
            color: white; font-family: Consolas; font-size: 16px; text-align: center;
            background: #df2020; border: 1px solid black; border-radius: 50%; 
            box-shadow: 1px 1px 5px #333; cursor: default; padding: 0 !important; margin: 0;
        }
        div#${preID}>.__link__clos_btn.__loading {
            animation: 3s linear 0s infinite rotating_button; background: #d76565;
        }
        div#${preID}>.__link__clos_btn.__loading:hover {
            animation: none; background: #df2020;
        }
        div#${preID}>.__link__clos_btn:hover {
            outline: 3px solid #be3737a2;
        }
        div#${preID}>.__link__clos_btn.__close {
            display: none;
        }
        /* 拖动手柄样式 */
        div#${preID}>div.__link__prev_mv {
            position: absolute; top: -5px; left: 0px; z-index: 10010;
            height: 6px; width: 30px; border: 1px solid #242424; background: #3e3e3e; cursor: move;
            border-radius: 3px; overflow: hidden; max-width: ${winWidth}px;
            font-size: 14px !important; color: #d7d7d7; line-height: 19px;
        }
        div#${preID}>div.__link__prev_mv>* {
            display: inline-block;
        }
        div#${preID}:hover>div.__link__prev_mv {
            height: 20px; top: -20px; width: fit-content;
        }
        div#${preID}>div.__link__prev_mv:hover {
            color: #e0a52e;
        }
        div#${preID}.__onmove>div.__link__prev_mv {
            background: #6f6122; height: 30px; top: -20px; width: fit-content;
        }
        div#${preID}.__loading>div.__link__prev_mv {
            background: #aa9537;
        }
        div#${preID} .__link__prev_ttl {
            font-size: 14px !important; font-family: Arial; white-space: nowrap; margin: 0 3px; width: fit-content;
        }
        div#${preID}>.__link__prev_rs {
            height: 4px; width: 4px; border: 1px solid #616161; color: #9a9276; font-size: 13px;
            position: absolute; right: 0; bottom: 0; z-index: 10011; background: #3e3e3e67;
            transition: 0.3s ease-out; font-size: 10px; line-height: 10px; overflow: hidden;
            cursor: nw-resize;
        }
        div#${preID}:hover>.__link__prev_rs {
            background: #3e3e3e; height: 12px; width: 12px; 
        }
        div#${preID}>.__link__prev_rs:hover {
            border-color: #2c96e2af; height: 18px; width: 18px;
        }
        div#${preID}.__onsize>.__link__prev_rs {
            height: 40px; width: 60px; transition: 0s; color: #e0a52e
        }
        /* 钉住按钮样式 */
        div#${preID} .__link__prev_pbtn {
            height: 18px; width: 20px !important; box-sizing: border-box; border-width: 0 1px; border-style: solid;
            vertical-align: bottom; margin: 0 4px; text-align: center; cursor: default;
        }
        div#${preID} .__link__prev_pbtn:hover {
            border-color: #f1db27; color: #f1db27; background: #ffffff36;
        }
        div#${preID} .__link__prev_pbtn:active {
            background: #ffffff82;
        }
        div#${preID}.__pinned .__link__prev_pbtn {
            background: #f5bc4f82; font-weight: 800;
        }
    `;
    addCSS(css, id);                                            // 添加的style节点添加id,用于判断是否需重添加

    var previewwin, pre_ir, pinbtn, closebtn, movehand, titlebox, ondrag, resizehand, rsdrag;
    var timer, linkprev, linkprevold, mousepos;
    var site = location.host;
    makeprevwin();
    setTimeout(function () {                                        // 延时启动
        document.addEventListener('mouseover', (evt) => {           // 检查鼠标下的节点,查找链接添加图标
            const t = evt.target;
            if (t.tagName == "A" && ispagelink(t)) {
                addIconTo([t]);
            } else {
                var links = getLinksInThreeLayer(t);
                addIconTo(links);
            }
            mousepos = { x: evt.x, y: evt.y };
        });
        // 设置监视器,若标题变化则重新加载
        var watch = document.querySelector('title');
        new (window.MutationObserver || window.WebKitMutationObserver)(function (mutations) {
            // console.log('标题变了', document.title);
            if (!document.querySelector("#" + id)) {
                addCSS(css, id);
            }
            if (!document.querySelector("#" + preID)) {
                makeprevwin();
            }
            site = location.host;
        }).observe(watch, { childList: true, subtree: true, characterData: true });
    }, 1000);

    function addIconTo(links) {     // 往链接上添加图标;输入:链接的数组
        var lnkl = links.length;
        var exl = 0;
        // console.log(lnkl);
        for (let i = 0; i < lnkl; i++) {
            const lnk = links[i];
            if (lnk.classList.contains("___prevlink")) {    // 跳过已处理的链接
                continue;
            }
            lnk.classList.add("___prevlink");
            if (window.getComputedStyle(lnk).position == "static") {    // 若链接本身无position设置则添加定位样式
                lnk.classList.add("__pr");
            }
            var lnkposit = getTrueSize(lnk);                // 获取链接占位
            var lnklcposit = getLastChildSize(lnk);         // 获取链接末个子节点的占位
            var lcl, lcw;
            if (!lnklcposit) {
                lcl = lnkposit.l;
                lcw = lnkposit.w;
            } else {
                lcl = lnklcposit.l;
                lcw = lnklcposit.w;
            }
            var img;
            img = creaElemIn('img', lnk);
            // img.dataset.for = url;
            img.classList.add("___previcon");
            var adl = lcl - lnkposit.l + lcw + 4; // 末子节点右边界右4像素;如果被父节点遮挡或自身隐藏
            var adb = -0.3;
            if (lnkposit.r < (4 + iconsize) || window.getComputedStyle(lnk).overflow !== "visible") {
                adl = Math.min(adl, lnkposit.w - 5 - iconsize); // 则避开遮挡
                adb = 0.05;
            }
            img.style.left = adl + "px";
            img.style.bottom = adb + "lh";
            if (domainregex.exec(lnk.href)[0] !== site) {   // 站内还是站外链接
                img.src = ixonimg;
                exl += 1;
            } else {
                img.src = inonimg;
            }
            if (lnkposit.w <= (iconsize * 3)) {    // 链接宽度不大于三倍图标大小则增加延时
                img.classList.add("__moredelay");
            }
            img.addEventListener('mouseover', (evt) => {    // 鼠标悬停图标上的动作
                //clearTimeout(timer);
                if (linkprevold == evt.target.parentNode) {
                    return;
                }
                var od = delaysec;
                if (evt.target.classList.contains("__moredelay")) {
                    od += moredelaysec;
                }
                od *= 1000;
                if (!!linkprev) {                           // 若有链接在预览中,去除其预览中状态
                    linkprev.classList.remove("__link__preved");
                }
                linkprev = evt.target.parentNode;           // 指定预览目标链接
                setTimeout(() => {
                    if (previewwin.classList.contains("__close")) { // 若预览窗关闭状态则移动到鼠标下
                        setWinPosit(mousepos.x, mousepos.y);
                    }
                }, od);
                timer = setTimeout(previewlink, od); // 一秒后开启预览窗
            });
            img.addEventListener('mouseleave', () => {      // 鼠标离开鼠标则清除计时
                clearTimeout(timer);
            });
        }
        //console.log("external link count:" + exl);

        window.addEventListener('mousedown', (e) => {       // 点击页面的动作
            var target = e.target;
            if (isPrevVisual() && !pinned && !previewwin.contains(target)) {
                hidepreview(e);   // 若预览窗开启且非钉住状态且点击位置不在预览窗内则隐藏预览窗
            }
        }, true);
    }

    function makeprevwin() {        // 生成预览窗
        previewwin = creaElemIn('div', document.body);
        previewwin.id = preID;
        previewwin.style.width = '300px';  // 显示前预览窗大小,显示时形成放大效果
        previewwin.style.height = '200px';
        previewwin.classList.add("__close");    // 初始状态关闭

        movehand = creaElemIn('div', previewwin);   // 移动手柄
        movehand.classList.add("__link__prev_mv");
        movehand.innerHTML = pre;

        resizehand = creaElemIn('div', previewwin); // 调整大小手柄
        resizehand.className = "__link__prev_rs";

        pinbtn = creaElemIn('div', movehand);     // 钉住按钮
        pinbtn.className = "__link__prev_pbtn";
        pinbtn.innerHTML = "T";
        pinbtn.addEventListener('click', togglePin);

        titlebox = creaElemIn('div', movehand);     // 标题栏
        titlebox.className = "__link__prev_ttl";

        pre_ir = creaElemIn('iframe', previewwin);  // 预览容器iframe
        pre_ir.className = "__link__prev_ifr";
        pre_ir.style.border = 0;
        pre_ir.style.maxHeight = "unset";
        pre_ir.style.maxWidth = "unset";
        pre_ir.style.height = 100 / scale + "%";
        pre_ir.style.width = 100 / scale + "%";

        closebtn = creaElemIn('div', previewwin);   // 关闭按钮
        closebtn.classList.add("__link__clos_btn");
        closebtn.innerHTML = "✖";
        closebtn.addEventListener('click', hidepreview);

        ondrag = endrag(previewwin, { x: 'left', y: 'top' }, movehand); // 绑定移动预览窗对象
        ondrag.hook('__drag_begin', () => {         // 绑定移动开始、结束状态样式
            previewwin.classList.add("__onmove");
        });
        ondrag.hook('__drag_end', () => {
            previewwin.classList.remove("__onmove");
        });

        rsdrag = endrag(previewwin, { x: 'width', y: 'height' }, resizehand);   // 绑定调整预览窗大小对象
        rsdrag.hook('__dragging', () => {           // 调整中更新iframe大小,记录预览窗大小
            if (!!rsdrag.position) {                            // 未开始拖动前该对象不存在,判断避免编译出错
                winWidth = rsdrag.position._x;
                winHeight = rsdrag.position._y;
                setIfrWinSize(winWidth, winHeight);
            }
            if (previewwin.classList.contains("__onsize")) {    // 调整中显示当前大小(该状态需加入已开始判断)
                // console.log("dragging", rsdrag.position.x, rsdrag.position.y);
                resizehand.innerHTML = `
                    W: ${winWidth} px <br />H: ${winHeight} px
                `;
            }
        });
        rsdrag.hook('__drag_begin', () => {         // 绑定调整大小开始、结束状态样式、显示
            previewwin.classList.add("__onsize");
        });
        rsdrag.hook('__drag_end', () => {
            previewwin.classList.remove("__onsize");
            resizehand.innerHTML = "";
        });
    }

    function previewlink() {             // 在预览窗中加载链接
        // console.log('linkprev: ', linkprev);
        linkprev.classList.add("__link__preved");
        linkprevold = linkprev;
        // var url = linkprev.getAttribute('url') || linkprev.href;
        var url = linkprev.href;
        // console.log('url: ', url);
        if (ualen > 0) {                        // 链接替换
            for (let i = 0; i < ualen; i++) {
                const ur = urlplaceArr[i];
                if (ur.scrurl.test(url)) {
                    const urid = ur.coreID.exec(url);
                    if (!!urid) {
                        url = ur.desturl.replace("--coreID--", urid[0]);
                        pre_ir.allow = "autoplay";
                    }
                }
            }
        }
        pre_ir.src = url;                           // 加载页面,调整缩放
        pre_ir.style.transform = "scale(" + scale + ")";
        previewwin.classList.toggle("__close", false);     // 预览窗去除关闭、添加加载中状态
        previewwin.classList.toggle("__loading", true);
        titlebox.innerHTML = linkprev.textContent;  // 以链接文本为标题
        var mx = mousepos.x, my = mousepos.y;
        setTimeout(() => {                          // 延时0.1秒(等待预览窗到位)显示、移动、大小
            previewwin.classList.toggle("__visible", true);
            if (!pinned) {
                if (mx > (window.innerWidth - winWidth - minHgap)) {
                    mx = mx - winWidth - 40;
                }
                if (my > (window.innerHeight - winHeight - minVgap)) {
                    my = my - winHeight - 15;
                }
                setWinPosit(Math.max(minHgap, mx), Math.max(minVgap, my));
            }
            setPrevWinSize(winWidth, winHeight);
            closebtn.classList.remove("__close");
            closebtn.classList.add("__loading");
        }, 100);
        pre_ir.onload = () => {                     // iframe加载完成后去除加载中状态
            closebtn.classList.remove("__loading");
            previewwin.classList.remove("__loading");
        };
    }

    function setWinPosit(l, t, toscreen) {            // 根据屏幕位置设置预览窗网页位置;toscreen 为true则设为屏幕位置
        var f = (toscreen) ? 0 : 1;
        previewwin.style.left = l + f * window.scrollX + 'px';
        previewwin.style.top = t + f * window.scrollY + 'px';
    }

    function setPrevWinSize(w, h) {         // 设置预览窗口大小
        previewwin.style.width = w + 'px';
        previewwin.style.height = h + 'px';
        setIfrWinSize(w, h);
    }

    function setIfrWinSize(w, h) {         // 根据放大率设置iframe窗口大小
        var f = (1 - scale) / 2 * -1 / scale;
        pre_ir.width = w / scale;
        pre_ir.height = h / scale;
        pre_ir.style.marginLeft = (w * f) + 'px';
        pre_ir.style.marginTop = (h * f) + 'px';
    }

    function togglePin() {                  // 切换钉住状态
        pinned = !pinned;
        var p = previewwin.getBoundingClientRect();         // 获取预览窗当前位置
        previewwin.classList.toggle('__onpin', true);       // 钉住“切换中”状态,以避免取消钉住后过快恢复动画
        previewwin.classList.toggle('__pinned', pinned);    // 已钉住状态
        setWinPosit(p.left, p.top, pinned);
        setTimeout(() => {
            previewwin.classList.toggle('__onpin', false);  // 钉住“切换中”状态结束
        }, 100);
    }

    function hidepreview(evt) {
        pinned = false;
        previewwin.classList.toggle('__pinned', pinned);
        setPrevWinSize(100, 100);           // 预览窗缩小效果
        pre_ir.src = '';
        setWinPosit(evt.x, evt.y);          // 在鼠标位置消失
        previewwin.classList.remove('__visible');
        linkprev.classList.remove('__link__preved');    // 去除链接高亮中状态
        closebtn.classList.add("__close");
        setTimeout(() => {                  // 动画效果完成后添加关闭状态
            previewwin.classList.add("__close");
        }, animationtime * 1000 + 10);
    }

    function isPrevVisual() {               // 预览窗是否可见
        return previewwin && previewwin.classList.contains('__visible');
    }

    function getLinksInThreeLayer(elem) {   // 对当前元素及其下两层的链接进行提取
        return getLinksInChild(elem, 3);
    }

    function getLinksInChild(elem, lcnt) {  // 递归提取元素中的链接
        var lnks = [];
        if (elem.childNodes.length == 0) {
            return lnks;
        } else {
            elem.childNodes.forEach((cnode) => {
                if ((cnode.tagName == "A") && isvisible(cnode)) {
                    if (ispagelink(cnode)) {
                        lnks.push(cnode);
                    } else {
                        if (lcnt - 1 > 0) {
                            lnks.concat(getLinksInChild(cnode, lcnt - 1));
                        }
                    }
                }
            });
        }
        return lnks;
    }

    function ispagelink(node) {             // 若A节点是指向实网址(非js非锚点)返回true
        if (!node.href) {
            return false;
        }
        const h = node.href, l = location.href;
        if (h.indexOf('javascript:') == 0 || h.replace(l.split('#')[0], "").indexOf("#") == 0) {
            return false;
        } else {
            return true;
        }
    }

    function isvisible(node) {              // 若节点非隐藏状态返回true
        var p = node.getBoundingClientRect();
        if ((p.width == 0 && p.height == 0) || (p.top == 0 && p.bottom == 0)) {
            return false;
        }
        return true;
    }

    function creaElemIn(tagname, destin) {	//在 destin 内末尾创建元素 tagname
        let theElem = destin.appendChild(document.createElement(tagname));
        return theElem;
    }

    function addCSS(css, cssid) {           // 创建带id的 style 节点
        let stylenode = creaElemIn('style', document.getElementsByTagName('head')[0]);
        stylenode.textContent = css;
        stylenode.type = 'text/css';
        stylenode.id = cssid || '';
    }

    function getLastChildSize(elem) {       // 返回节点的最后一个子节点的实占位对象
        var cs = elem.childNodes;
        if (cs.length == 0) {
            return false;
        }
        var lc = cs[cs.length - 1];
        if (lc.nodeType == 3) {
            return getTextNodeSize(lc);
        } else {
            return getTrueSize(lc);
        }
    }

    // 输入元素,返回实占位对象{元素可见的宽、高、顶、左、一层父元素右侧余量}
    function getTrueSize(elem, posiz) {
        if (!posiz) {
            var p = elem.getBoundingClientRect();
            posiz = {
                w: p.width,
                h: p.height,
                t: p.top,
                l: p.left,
                r: 0,
            };
        }
        var pp = elem.parentNode.getBoundingClientRect();
        var pr = posiz.l + posiz.w, pb = posiz.t + posiz.h;
        if (posiz.r == 0) posiz.r = pp.right - pr;                  // 父元素对当前元素的右侧余量
        var isvi = {
            l: posiz.l < pp.right && posiz.l >= pp.left,            // 子左在父左右之间
            r: pr > pp.left && pr <= pp.right,                      // 子右在父左右之间
            t: posiz.t < pp.bottom && posiz.t >= pp.top,            // 子顶在父顶底之间
            b: pb > pp.top && pb <= pp.bottom                       // 子底在父顶底之间
        };
        if (isvi.l && isvi.r && isvi.t && isvi.b) {                 // 子全在父之内,则返回子占位
            return posiz;
        } else {
            var ppl = (isvi.l) ? posiz.l : pp.left;                 // 确定可见四边(在父之内按子,否则按父)
            var ppt = (isvi.t) ? posiz.t : pp.top;
            var ppr = (isvi.r) ? posiz.l + posiz.w : pp.right;
            var ppb = (isvi.b) ? posiz.t + posiz.h : pp.bottom;
            return getTrueSize(elem.parentNode, {
                w: ppr - ppl,
                h: ppb - ppt,
                t: ppt,
                l: ppl,
                r: posiz.r
            });
        }
    }

    // 输入文本节点,返回实占位对象{文本节点的宽、高、顶、左、一层父元素右侧余量}
    function getTextNodeSize(textNode) {
        if (textNode.nodeType !== 3) {
            return false;
        }
        if (document.createRange) {
            var range = document.createRange();
            range.selectNodeContents(textNode);
            if (range.getBoundingClientRect) {
                var rect = range.getBoundingClientRect();
                if (rect) {
                    var posit = {
                        w: rect.width,
                        h: rect.height,
                        t: rect.top,
                        l: rect.left,
                        r: 0
                    };
                    return getTrueSize(textNode, posit);
                }
            }
        }
        return false;
    }

    // 对target拖动handle时,实现拖动的功能
    // 输入:目标元素target,拖动位置参考系opt,拖动手柄handle
    // 输入opt:形如【{x:'right',y:'bottom'}】,或者width、height(右下角拖动)
    function endrag(target, opt, handle) {
        var p_x, p_y, isDragging;
        endrag = function (target, opt, handle) {
            return new endrag.proto(target, opt || {}, handle);
        }
        endrag.proto = function (target, opt, handle) {
            var self = this;
            this.target = target;
            this.style = target.style;
            this.handle = handle;
            var _x = opt.x !== 'right';
            var _y = opt.y !== 'bottom';
            this.x = opt.x;  //_x ? 'left' : 'right';
            this.y = opt.y;  //_y ? 'top' : 'bottom';
            // p_x = this.x;
            // p_y = this.y;
            this.xd = _x ? -1 : 1;
            this.yd = _y ? -1 : 1;
            this.computed_style = document.defaultView.getComputedStyle(target, '');
            this.drag_begin = function (e) { self.__drag_begin(e); };
            this.handle.addEventListener('mousedown', this.drag_begin, false); //only drag on handler
            this.dragging = function (e) { self.__dragging(e); };
            document.addEventListener('mousemove', this.dragging, false);
            this.drag_end = function (e) { self.__drag_end(e); };
            document.addEventListener('mouseup', this.drag_end, false);
        };
        endrag.proto.prototype = {
            __drag_begin: function (e) {
                if (e.button == 0) {
                    var _c = this.computed_style;
                    this.isDragging = isDragging = true;
                    this.position = {
                        _x: parseFloat(_c[this.x]),
                        _y: parseFloat(_c[this.y]),
                        x: e.pageX,
                        y: e.pageY
                    };
                    e.preventDefault();
                }
            },
            __dragging: function (e) {
                if (!this.isDragging) return;
                var x = Math.floor(e.pageX), y = Math.floor(e.pageY), p = this.position;
                // prevent moving out of window
                var x_border = window.innerWidth - 40, y_border = window.innerHeight - 20;
                if (x - window.scrollX > x_border) x = window.scrollX + x_border;
                if (y - window.scrollY > y_border) y = window.scrollY + y_border;
                p._x = p._x + (p.x - x) * this.xd;
                p._y = p._y + (p.y - y) * this.yd;
                this.style[this.x] = p._x + 'px';
                this.style[this.y] = p._y + 'px';
                p.x = x;
                p.y = y;
            },
            __drag_end: function (e) {
                if (e.button == 0) {
                    if (this.isDragging) {
                        this.isDragging = isDragging = false;
                    }
                }
            },
            hook: function (method, func) {
                if (typeof this[method] === 'function') {
                    var o = this[method];
                    this[method] = function () {
                        if (func.apply(this, arguments) === false) {
                            return;
                        }
                        o.apply(this, arguments);
                    };
                }
            }
        };
        return endrag(target, opt, handle);
    }


})();