Greasy Fork is available in English.

链接预览

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

// ==UserScript==
// @name         链接预览
// @name:zh-cn   链接预览
// @name:en      Link Previewer
// @namespace    https://greasyfork.org/zh-CN/users/1073-hzhbest
// @version      2.4
// @description  鼠标指向链接标识图标预览链接网页
// @description:zh-cn  鼠标指向链接标识图标预览链接网页
// @description:en Hovering to preview a link
// @author       hzhbest
// @match        http*://*/*
// @exclude      https://mega.nz/file/*
// @exclude      https://*.github.com/*
// @exclude      https://addons.mozilla.org/*
// @grant        GM_registerMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_openInTab
// @license      MIT
// @run-at       document-end
// ==/UserScript==

// 2.4: 修正悬停预览模式下链接预览样式被误触发的问题,修正已有钉住的预览窗时依然随新链接移动的问题
// TODO: 如何应对会修改链接classname的网页?用“has()”选择器??

(function () {
    'use strict';

    // --------自定义区-------- //
    const scriptname = "链接预览";
    const recordPrevHist = false;           // 将预览链接记入历史开关:Boolean
    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 = "░░░░░░ ";                  // 移动手柄显示:字符串
    var delaysec = 1;                       // 触发预览等候延时:秒
    const moredelaysec = 1;                 // 对小链接增加等候延时:秒
    const showLeftSizer = false;            // 显示左侧调整大小手柄:Boolean
    const closeOnScoll = false;             // 在预览窗外滚动滚轮关闭预览窗:Boolean
    var defaultpinned = 0;                  // 初始钉住状态:{0:否,1:是}
    var defaultenabled = 1;                 // 初始启用状态:{0:否,1:是}
    const toggleHotkey = "C-M-v";           // 切换启用状态快捷键(Ctrl:“C-”,Alt/⌥:“M-”,字母键大写则代表含Shift键)
    var onclk = 0;                          // 是否点击预览默认值:{0:否,1:是}
    const specialLnkArr = [                 // 因链接所在元素含position样式属性而需排除的特征数组
        {
            name: "Google主链接",
            scrurl: /google\.com\/search\?/,
            linkslc: "#rso .g a:has(h3)"
        }
    ];
    const urlplaceArr = [                    // 网址替换规则数组,{命名,链接网址正则,关键编号正则,替换网址模板}
        {
            name: "B站",
            scrurl: /bilibili\.com\//,
            coreID: /(av|BV)[^\?&\/]+/,
            desturl: "https://player.bilibili.com/player.html?bvid=--coreID--&high_quality=1&autoplay=1"
            // desturl: "https://www.bilibili.com/blackboard/html5mobileplayer.html?bvid=--coreID--&autoplay=1" //2024-04-28发现不会自动播放
        },
        {
            name: "油管",
            scrurl: /youtube\.com\//,
            coreID: /(?<=(watch\?v=|\/shorts\/|\/live\/))[^&\/]+/,
            desturl: "https://www.youtube.com/embed/--coreID--"
        },
        {
            name: "油管短",
            scrurl: /youtu\.be\//,
            coreID: /(?<=(\.be\/))[^&\/]+/,
            desturl: "https://www.youtube.com/embed/--coreID--"
        },
        {
            name: "西瓜",
            scrurl: /ixigua\.com\//,
            coreID: /(?<=\/)\d+\?/,
            desturl: "https://www.ixigua.com/iframe/--coreID--autoplay=1"
        },
        {
            name: "PH",
            scrurl: /pornhub\.com\//,
            coreID: /(?<=viewkey=).+/,
            desturl: "https://www.pornhub.com/embed/--coreID--"
        }
    ];
    // --------自定义区结束--------//

    const preID = "__link__prev_win"        // 预览窗id
    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 _txt = {
        swc: [["hover", "click"], ["悬停", "点击"]],
        ntc: ["Switched to --swc-- preview mode", "已切换到--swc--预览模式"],
        gmc: ["Toggle preview mode", "切换预览模式"],
        swp: [["Pinless", "Pinned"], ["非钉住", "钉住"]],
        ntp: ["Switched to --swp-- mode by default", "已切换到默认--swp--模式"],
        gmp: ["Toggle default pinned", "切换默认钉住模式"],
        enpin: ["Pin Preview Win", "钉住预览窗"],
        unpin: ["Unpin Preview Win", "解钉预览窗"],
        optab: ["Open this page in a new tab", "在新标签页打开当中页面"],
        swt: [["disable", "enable"], ["禁用", "启用"]],
        ntt: ["Switched to --swt-- status", "已切换到--swt--状态"],
        gmt: ["Toggle preview status", "切换预览状态"]
    }
    //language detection
    const _L = (navigator.language.indexOf('zh-') == -1) ? 0 : 1;
    const isonclick = "Link_preview_on_click";
    const isdefaultpin = "Link_preview_default_pinned";
    const isdefaultenable = "Link_preview_default_enabled";
    onclk = GM_getValue(isonclick, onclk);
    defaultpinned = GM_getValue(isdefaultpin, defaultpinned);
    defaultenabled = GM_getValue(isdefaultenable, defaultenabled);
    document.body.classList.toggle("__link_pre_clk", (onclk == 1));     // 全局点击样式标志
    document.body.classList.toggle("__link_pre_disable", (defaultenabled == 0));     // 全局禁用样式标志
    delaysec = (onclk) ? delaysec / 2 : delaysec;
    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;
            max-width: initial !important; max-height: initial !important;
        }
        a.___prevlink>img.___previcon:hover {
            background: #3e3ed3 !important; filter: invert(1) hue-rotate(150deg);
            transition: background ${delaysec}s, filter ${delaysec}s !important;
        }
        body.__link_pre_clk a.___prevlink>img.___previcon:hover {
            background: #c652e6 !important; outline: 2px solid #76268d !important; filter: invert(1) hue-rotate(150deg);
            transition: background 0.1s, filter 0.1s !important;
        }
        body.__link_pre_disable a.___prevlink>img.___previcon {
            display: none !important;
        }
        a.___prevlink>img.___previcon.__moredelay:hover, body.__link_pre_clk a.___prevlink>img.___previcon.__moredelay:hover {
            transition: background ${delaysec + moredelaysec}s, filter ${delaysec + moredelaysec}s, outline 0s ease ${delaysec + moredelaysec}s !important;
            outline: 2px solid #76268d !important;
        }
        *:hover>a.___prevlink>img.___previcon {
            opacity: 30%;
        }
        a.___prevlink:hover {
            outline: 2px dotted #939393;
        }
        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: 1000001; box-shadow: 1px 1px 7px 1px #717171;
            padding: 0; margin: 0; background-color: #ffffff; 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 #d8db7c !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;
        }
        /* 关闭按钮样式 */
        @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: 95%;
            font-size: 14px !important; color: #d7d7d7; line-height: 19px;
            display: grid; grid-template-columns: auto auto auto 1fr;
        }
        div#${preID}>div.__link__prev_mv>* {
            display: block;
        }
        div#${preID}:hover>div.__link__prev_mv {
            height: 20px; top: -20px; width: auto;
        }
        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; overflow: hidden;
            text-overflow: ellipsis; width: 100%;
        }
        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: nwse-resize;
        }
        div#${preID}>.__link__prev_rs.__rs_l {
            left: 0; right: unset !important; cursor: nesw-resize !important;
        }
        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.__onsize {
            height: 40px; width: 60px; transition: 0s; color: #e0a52e
        }
        /* 顶栏按钮样式 */
        div#${preID} .__link__prev_btn {
            height: 18px; width: 18px !important; box-sizing: content-box; border-width: 0 1px; border-style: solid;
            vertical-align: bottom; padding: 0 6px;text-align: center; cursor: pointer;
        }
        div#${preID} .__link__prev_btn:hover {
            border-color: #f1db27; color: #f1db27; background: #ffffff36;
        }
        div#${preID} .__link__prev_btn:active {
            background: #ffffff82;
        }
        div#${preID}.__pinned .__link__prev_btn.__pinbtn {
            background: #f5bc4f82; font-weight: 800;
        }
    `;
    addCSS(css, id);                                            // 添加的style节点添加id,用于判断是否需重添加
    window.addEventListener('load', function () {
        GM_registerMenuCommand(_txt.gmc[_L], changemode);
        GM_registerMenuCommand(_txt.gmp[_L], changedfpin);
        GM_registerMenuCommand(_txt.gmt[_L], changestatus);
    }, false);
    document.addEventListener('keydown', keyhandler, false);
    var specSiteInd = -1;
    for (let i = 0; i < specialLnkArr.length; i++) {
        if (specialLnkArr[i].scrurl.test(location.href)) {
            specSiteInd = i;
            break;
        }
    }
    var previewwin, pre_ir, pinbtn, closebtn, movehand, titlebox, ondrag, resizehand, resizehand2, openbtn;
    var rsdrag, rsldrag, ldragR, pinned;
    var timer, linkprev, prevedUrl, mousepos, vscale, moretimer;
    var site = location.host, isiniframe = false, isinprevwin = false, prevwinurl;
    pinned = (defaultpinned == 1);

    if (window.self != window.top) {    // 只在iframe中使用的事件处理
        isiniframe = true;
        window.addEventListener('message', (e) => {  // 接收来自预览窗的信息,若有,则标识为预览窗内并回发消息
            if (e.data.from == '__link__prev_top' && !!e.data.posit) {
                isinprevwin = true;
                window.top.postMessage({
                    from: '__link__prev_inwin',
                    url: window.self.location.href
                }, '*');
                makeprevwin();                  // 在预览窗内嵌套预览窗
            }
        }, true);
    } else {
        makeprevwin();                  // 在顶层窗口建预览窗
        document.addEventListener('keydown', (evt) => {
            if (evt.key == "Escape" && isPrevVisual()) {    // ESC键按下时若预览窗可见,则关闭预览窗
                hidepreview();
            }
        });
        window.addEventListener('message', function (e) {   // 处理iframe发来的消息
            if (e.data.from == '__link__prev_inwin') {
                if (e.data.url && isPrevVisual()) {         // 向预览窗传输iframe中的实际网址
                    openbtn.dataset.url = e.data.url;
                    linkprev.classList.add("__link__preved");   // 预览成功再显示链接被预览的状态
                }
            } else if (e.data.from == '__link__prev_iframe') {
                if (!!e.data.prevhref) {
                    callprevwin(e.data.prevod, e.data.prevhref, e.data.prevcont);
                }
            }
        }, true);
    }

    setTimeout(function () {                                        // 延时启动
        document.addEventListener('mouseover', (evt) => {           // 检查鼠标下的节点,查找链接添加图标
            const t = evt.target;
            // console.log('overed>: ', t);
            if (t.tagName == "A" && ispagelink(t)) {
                addIconTo([t]);
            } else {
                var links = getLinksInThreeLayer(t);
                // console.log('links(1): ', links);
                if (links.length == 0) {
                    links = getLinkInParent(t);
                }
                // console.log('links(2): ', links);
                if (links.length > 0) {
                    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) {     // 往链接上添加图标;输入:链接的数组
        // console.log('going2addIcon::links: ', 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")) {    // 包括被抹掉了a的样式标志
                lnk.classList.toggle("___prevlink", true);
            } else if (!!lnk.querySelector('img.___previcon')) {    // 跳过已有图标的链接
                continue;
            }

            var islinkpositionable = true;
            if (specSiteInd > -1) {
                if ([...document.querySelectorAll(specialLnkArr[specSiteInd].linkslc)].includes(lnk)) { // 若为特殊网站特殊链接则不添加定位样式
                    islinkpositionable = false;
                }
            }
            if (islinkpositionable && window.getComputedStyle(lnk).position == "static") {    // 若链接本身无position设置则添加定位样式
                lnk.classList.add("__pr");
            }

            var lnklcposit = getLastChildSize(lnk);         // 获取链接末个子节点的占位
            var lnkposit = getTrueSize(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 * 10) && lnkposit.h <= (iconsize * 4) && adl < lnkposit.w) {    // 链接不宽不高且图标位于链接内的话
                img.classList.add("__moredelay");
            }
            img.addEventListener('mouseover', (evt) => {    // 鼠标悬停图标上的动作
                var etarget = evt.target;
                if (onclk == 1) {                   // 点击预览模式时,对小链接增加延时,延时后点击才预览
                    if (etarget.classList.contains("__moredelay")) {
                        moretimer = setTimeout(() => {
                            etarget.classList.add("__moredelayfin");
                        }, moredelaysec * 1000);
                    }
                    return;
                }
                var od = delaysec;
                if (evt.target.classList.contains("__moredelay")) {
                    od += moredelaysec;
                }
                od *= 1000;
                previewEvent(evt, od);
            });
            img.addEventListener('click', (evt) => {    // 鼠标点击图标的动作
                if (onclk == 0 || (evt.target.classList.contains("__moredelay") && !evt.target.classList.contains("__moredelayfin"))) {
                    return;
                }
                clearTimeout(timer);
                evt.preventDefault();
                evt.stopPropagation();
                previewEvent(evt, 0);
            });
            img.addEventListener('mouseleave', (evt) => {      // 鼠标离开图标则清除计时
                if (evt.target.classList.contains("__moredelayfin")) {
                    evt.target.classList.remove("__moredelayfin");
                }
                clearTimeout(timer);
                clearTimeout(moretimer);
            });
        }
        //console.log("external link count:" + exl);

        window.addEventListener('mousedown', (e) => {       // 点击页面的动作
            var t = e.target;
            if (isPrevVisual() && !pinned && !previewwin.contains(t) && !t.classList.contains("___previcon")) {
                hidepreview(e);   // 若预览窗开启且非钉住状态且点击位置不在预览窗内或预览图标上则隐藏预览窗
            }
        }, true);
        if (closeOnScoll) {
            window.addEventListener('scroll', () => {       // 滚动页面的动作
                if (isPrevVisual() && !pinned) {
                    hidepreview();   // 若预览窗开启且非钉住状态且滚动位置不在预览窗内(window天然隔离iframe)则隐藏预览窗
                }
            }, true);
        }
    }

    function previewEvent(evt, od) {                        // 触发预览
        if (prevedUrl == evt.target.parentNode.href && isPrevVisual()) {  // 已在预览的链接相同则无视
            return;
        }
        let nowprev = document.querySelector('.__link__preved');
        if (!!nowprev) {                            // 若有链接在预览中,去除其预览中状态
            nowprev.classList.remove("__link__preved");
        }
        linkprev = evt.target.parentNode;           // 指定预览目标链接
        if (isiniframe && !isinprevwin) {           // 在iframe中且非预览窗中,则发送消息
            window.top.postMessage({
                from: '__link__prev_iframe',
                prevhref: linkprev.href,
                prevcont: linkprev.textContent,
                prevod: od
            }, '*');
        } else {                                    // 否则直接调预览窗
            callprevwin(od);
        }
    }

    function callprevwin(od, prevhref, prevcont) {
        setTimeout(() => {
            if (previewwin.classList.contains("__close")) { // 若预览窗关闭状态则移动到鼠标下
                setWinPosit(mousepos.x, mousepos.y, pinned);
            }
        }, od);
        timer = setTimeout(previewlink, od, prevhref, prevcont); // od秒后开启预览窗
    }

    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");

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

        if (showLeftSizer) {
            resizehand2 = creaElemIn('div', previewwin); // 调整大小手柄
            resizehand2.className = "__link__prev_rs";
            resizehand2.classList.add("__rs_l");
        }

        var movehead = creaElemIn('div', movehand); // 移动手柄头
        movehead.innerHTML = pre;

        pinbtn = creaElemIn('div', movehand);     // 钉住按钮
        pinbtn.classList.add("__link__prev_btn");
        pinbtn.classList.add("__pinbtn");
        pinbtn.innerHTML = "T";
        pinbtn.title = (pinned) ? _txt.unpin[_L] : _txt.enpin[_L];
        pinbtn.addEventListener('click', togglePin);

        openbtn = creaElemIn('div', movehand);     // 新标签页打开按钮
        openbtn.classList.add("__link__prev_btn");
        openbtn.classList.add("__opnbtn");
        openbtn.innerHTML = "↗";
        openbtn.title = _txt.optab[_L];
        openbtn.addEventListener('click', (evt) => {
            if (!!evt.target.dataset.url) {
                openInTab(evt.target.dataset.url);
            }
        });

        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";

        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.isDragging) {               // 不加这句则会在鼠标移上时触发
                return;
            }
            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");
            resizehand.classList.add("__onsize");
        });
        rsdrag.hook('__drag_end', () => {
            previewwin.classList.remove("__onsize");
            resizehand.classList.remove("__onsize");
            resizehand.innerHTML = "";
        });

        if (showLeftSizer) {
            rsldrag = endrag(previewwin, { x: 'left', y: 'height' }, resizehand2);   // 绑定左侧调整预览窗大小对象
            rsldrag.hook('__dragging', () => {           // 调整中更新iframe大小,记录预览窗大小
                if (!rsldrag.isDragging) {
                    return;
                }
                if (!!rsldrag.position) {     // 未开始拖动前该对象不存在,判断避免解释出错
                    winWidth = ldragR - rsldrag.position._x;
                    winHeight = rsldrag.position._y;
                    setPrevWinSize(winWidth, winHeight);
                    setWinPosit(rsldrag.position._x, previewwin.getBoundingClientRect().top, pinned);   // 同步调整左边位置
                }
                if (previewwin.classList.contains("__onsize")) {    // 调整中显示当前大小(该状态需加入已开始判断)
                    // console.log("dragging", rsldrag.position.x, rsldrag.position.y);
                    resizehand2.innerHTML = `
                        W: ${winWidth} px <br />H: ${winHeight} px
                    `;
                }
            });
            rsldrag.hook('__drag_begin', () => {         // 绑定调整大小开始、结束状态样式、显示
                if (!previewwin.classList.contains('__onsize')) {
                    ldragR = previewwin.getBoundingClientRect().right;
                }
                previewwin.classList.add("__onsize");
                resizehand2.classList.add("__onsize");
            });
            rsldrag.hook('__drag_end', () => {
                previewwin.classList.remove("__onsize");
                resizehand2.classList.remove("__onsize");
                resizehand2.innerHTML = "";
            });
        }
    }

    function previewlink(prevhref, prevcont) {             // 在预览窗中加载链接
        // console.log('linkprev: ', linkprev);
        if (!prevhref) {
            prevhref = linkprev.href;
            prevcont = linkprev.textContent;
        }
        prevedUrl = prevhref;
        // var url = linkprev.getAttribute('url') || linkprev.href;
        var url = prevedUrl;
        // console.log('url: ', url);
        vscale = scale;
        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.sandbox = "allow-top-navigation allow-same-origin allow-forms allow-scripts";
                        vscale = 1;
                        break;
                    }
                }
            }
        }
        var lh = location.href;
        pre_ir.src = url;                           // 加载页面,调整缩放
        var regSameOri = /[^\/:]+\:\/\/[^a-z0-9\.\:]\//;
        if (recordPrevHist && (regSameOri.exec(lh) == regSameOri.exec(url))) {
            history.pushState({ foo: "callback" }, null, url);     // 推送预览url到历史中
            history.replaceState({ foo: "callback" }, null, lh);     // 用当前页面地址替换历史state
        }
        pre_ir.style.transform = "scale(" + vscale + ")";
        pre_ir.style.height = 100 / vscale + "%";
        pre_ir.style.width = 100 / vscale + "%";
        previewwin.classList.toggle("__close", false);      // 预览窗去除关闭、添加加载中状态
        previewwin.classList.toggle("__loading", true);
        previewwin.classList.toggle('__pinned', pinned);
        titlebox.innerHTML = pre + prevcont;                // 以链接文本为标题
        titlebox.title = prevcont;
        if (pinned && isPrevVisual()) {
            var p = previewwin.getBoundingClientRect();
            wx = p.left;
            wy = p.top;
        } else {
            var mx = mousepos.x, my = mousepos.y, wx, wy;       // 预览窗位置
            if (mx > (window.innerWidth - winWidth - minHgap)) {
                mx = mx - winWidth - 40;
            }
            if (my > (window.innerHeight - winHeight - minVgap)) {
                my = my - winHeight - 15;
            }
            wx = Math.max(minHgap, mx);
            wy = Math.max(minVgap, my);
        }
        setTimeout(() => {                          // 延时0.1秒(等待预览窗到位)显示、移动、大小
            previewwin.classList.toggle("__visible", true);
            setWinPosit(wx, wy, pinned);
            setPrevWinSize(winWidth, winHeight);
            closebtn.classList.remove("__close");
            closebtn.classList.add("__loading");
        }, 100);
        pre_ir.onload = () => {                     // iframe加载完成后去除加载中状态
            closebtn.classList.remove("__loading");
            previewwin.classList.remove("__loading");
            pre_ir.contentWindow.postMessage({
                from: '__link__prev_top',
                posit: { wx, wy }
            }, '*');
        };
    }

    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 = (vscale == 1) ? 0 : (1 - vscale) / 2 * -1 / vscale;
        pre_ir.width = w / vscale;
        pre_ir.height = h / vscale;
        pre_ir.style.marginLeft = (w * f) + 'px';
        pre_ir.style.marginTop = (h * f) + 'px';
    }

    function togglePin(ispinned) {                  // 切换钉住状态
        if (typeof (ispinned) == "boolean") {
            pinned = ispinned;
        } else {
            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 openInTab(url) {
        GM_openInTab(url, { active: true });
        hidepreview();
    }

    function hidepreview(evt) {
        pinned = (defaultpinned == 1);
        pre_ir.src = '';
        var x, y;
        if (!evt) {
            x = mousepos.x;
            y = mousepos.y;
        } else {
            x = evt.x;
            y = evt.y;
        }
        setPrevWinSize(100, 100);           // 预览窗缩小效果
        setWinPosit(x, y, pinned);          // 在鼠标位置消失
        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 getLinkInParent(elem) {
        if (ispagelink(elem)) {
            return [elem];
        } else {
            let pelem = elem.parentNode;
            if (!elem || pelem.tagName == 'BODY') {
                return [];
            } else {
                return getLinkInParent(pelem);
            }
        }
    }

    function ispagelink(node) {             // 若A节点是指向实网址(非js非锚点)返回true
        if (!node.href || (!!node.target && !/_(self|blank|top)/.test(node.target))) {  // 无网址或有框架指向的链接(点击不刷新主体页面)不处理
            return false;
        }
        const h = node.href, l = location.href; // 仅处理非js非本页锚点
        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 changemode() {                 // 切换预览触发模式
        onclk = (onclk == 0) ? 1 : 0;
        const str = _txt.ntc[_L].replace('--swc--', _txt.swc[_L][onclk]);
        popupnotice(str);
        document.body.classList.toggle("__link_pre_clk", (onclk == 1));
        GM_setValue(isonclick, onclk);
    }

    function changedfpin() {                // 切换默认顶住状态
        defaultpinned = (defaultpinned == 0) ? 1 : 0;
        const str = _txt.ntp[_L].replace('--swp--', _txt.swp[_L][defaultpinned]);
        popupnotice(str);
        togglePin(defaultpinned == 1);
        GM_setValue(isdefaultpin, defaultpinned);
    }

    function changestatus() {
        defaultenabled = (defaultenabled == 0) ? 1 : 0;
        const str = _txt.ntt[_L].replace('--swt--', _txt.swt[_L][defaultenabled]);
        popupnotice(str);
        document.body.classList.toggle("__link_pre_disable", (defaultenabled == 0));
        GM_setValue(isdefaultenable, defaultenabled);
    }

    function keyhandler(evt) {
        var fullkey = get_key(evt);
        if (fullkey == toggleHotkey) {
            changestatus();
        }
    }

    // 按键evt.which转换为键名
    function get_key(evt) {
        const keyCodeStr = {			//key press 事件返回的which代码对应按键键名对应表对象
            8: 'BAC',
            9: 'TAB',
            10: 'RET',
            13: 'RET',
            27: 'ESC',
            33: 'PageUp',
            34: 'PageDown',
            35: 'End',
            36: 'Home',
            37: 'Left',
            38: 'Up',
            39: 'Right',
            40: 'Down',
            45: 'Insert',
            46: 'Delete',
            112: 'F1',
            113: 'F2',
            114: 'F3',
            115: 'F4',
            116: 'F5',
            117: 'F6',
            118: 'F7',
            119: 'F8',
            120: 'F9',
            121: 'F10',
            122: 'F11',
            123: 'F12'
        };
        const whichStr = {
            32: 'SPC'
        };
        var key = String.fromCharCode(evt.which),
            ctrl = evt.ctrlKey ? 'C-' : '',
            meta = (evt.metaKey || evt.altKey) ? 'M-' : '';
        if (!evt.shiftKey) {
            key = key.toLowerCase();
        }
        if (evt.ctrlKey && evt.which >= 186 && evt.which < 192) {
            key = String.fromCharCode(evt.which - 144);
        }
        if (evt.key && evt.key !== 'Enter' && !/^U\+/.test(evt.key)) {
            key = evt.key;
        } else if (evt.which !== evt.keyCode) {
            key = keyCodeStr[evt.keyCode] || whichStr[evt.which] || key;
        } else if (evt.which <= 32) {
            key = keyCodeStr[evt.keyCode] || whichStr[evt.which];
        }
        return ctrl + meta + key;
    }

    function popupnotice(str) {             // 在页面内显示弹出通知
        const vpos = 0.7, hpos = 0.5;
        let notice = creaElemIn('div', document.body);
        notice.innerHTML = scriptname + ":" + str;
        notice.style = `
            transform: translate(-50%,-50%); position: fixed; left: ${hpos * 100 + '%'}; top: ${vpos * 100 + '%'};
            border: none; outline: 3px solid white; box-shadow: 0 4px 7px 4px #3f3f3f;
            background: black; color: white; font-family: Arial; font-size: 14pt;
            transition: opacity 0.5s; opacity: 0; padding: 20px; border-radius: 15px;
            z-index: 2000000;
        `;
        setTimeout(() => {
            notice.style.opacity = 0.8;
        }, 500);
        setTimeout(() => {
            notice.style.opacity = 0;
        }, 5500);
        setTimeout(() => {
            removeNode(notice);
        }, 6000);
    }

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

    function removeNode(node) {
        if (!!node.parentNode) {
            node.parentNode.removeChild(node);
        }
    }

    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) return false;
        if (lc.nodeType == 3 && lc.length > 0) {
            var crect = elem.getClientRects();
            var rp = elem.getBoundingClientRect().right;
            if (crect.length > 1) {             // 若文本折行导致多个 client 的 rect 存在,则取最后一个有大小的实占
                for (let i = crect.length - 1; i >= 0; i--) {
                    const p = crect[i];
                    if (p.width > 0 && p.height > 0) {
                        return {
                            w: p.width,
                            h: p.height,
                            t: p.top,
                            l: p.left,
                            r: rp - p.right
                        };
                    }
                }
            } else {
                return getTextNodeSize(lc, rp);     // 不折行则直接取文本实占
            }
        } else {
            return getTrueSize(lc);             // 非文本或空文本则取整个实占
        }
    }

    // 输入元素,返回实占位对象{元素可见的宽、高、顶、左、一层父元素右侧余量}
    function getTrueSize(elem, posiz) {
        // console.log(elem);
        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, parentright) {
        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: parentright - rect.right
                    };
                    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);
    }


})();