内容展开显示

展开鼠标下方的内容。

// ==UserScript==
// @name        内容展开显示
// @namespace   hzhbest
// @include     *://*/*
// @description    展开鼠标下方的内容。
// @version     0.4
// @run-at      document-end
// @license     GNU GPLv3
// ==/UserScript==

(function () {

    'use strict';
    var videotimeout, popuptimeout, mvouttimeout;
    var popuptimer, mvouttimer, clstime;
    var curelem;
    var topbox, expbox;
    var firstopen, paused = false, vsrc, mt = "";
    var padding = 3;
    let css = `
		.expand_box {
            display: none; position: absolute; height: fit-content; max-width: 95%; height: auto; overflow-y: auto;
            scrollbar-width: thin; background: #fafbed; border: 1px solid #a8a8a8; box-shadow: 0 0 5px #222;
            z-index: 10000; padding: ${padding}px;
        }
        .expand_box>div {color: black !important;}
        .expand_box.shown {display: block !important;}
	`;

    // 设置选项 ===
    popuptimeout = 0.5;	// 鼠标悬停弹出延时(秒)
    mvouttimeout = 1;	// 鼠标移出关闭延时(秒)
    //videotimeout = 3;	// 视频播放完后自动关闭延时(秒);钉住时不关闭
    // 设置选项 ||=

    var scrolltimer;
    document.body.addEventListener('mousemove', (e) => {        // 按住shfit键悬停鼠标时弹出主容器
        if (!paused && e.shiftKey) {                            // 非暂停状态下
            clearTimeout(popuptimer);                           // 重置弹出计时(不再为前一个弹出计时)
            texpand(e);
        }
    }, false);
    var wheelEvt = "onwheel" in document.createElement("div") ? "wheel" : (document.onmousewheel !== undefined ? "mousewheel" : "DOMMouseScroll");	//compatibility fix for Chrome-core browsers
    document.body.addEventListener(wheelEvt, () => {
        paused = true;
        clearTimeout(scrolltimer);
        clearTimeout(popuptimer);
        scrolltimer = setTimeout(() => {
            paused = false;
        }, 1000);
    }, false);   // 鼠标滚动,进入暂停状态并延时取消
    document.body.addEventListener('mousedown', () => { paused = true; }, false);   // 鼠标按键落下,进入暂停状态
    document.body.addEventListener('mouseup', () => { paused = false; }, false);    // 鼠标按键弹起,取消暂停状态
    /*     document.body.addEventListener('mousemove', (event) => {
            if (event.shiftKey && isOverflow(event.target, event)) {
                popuptime = setTimeout(() => {
                    expandtext(event.target, getTrueSize(event.target));
                }, popuptimeout * 300);
            }
        }, false); */
    addCSS(css);
    topbox = creaElemIn('div', document.body);                  // 主容器
    topbox.className = 'expand_box';
    expbox = creaElemIn('div', topbox);                         // 内容容器
    firstopen = false;                                          // 主容器是否“新的”

    topbox.addEventListener('mouseleave', (event) => {          // 鼠标移出主容器时,计时隐藏主容器
        firstopen = false;                                      // 主容器变“旧”
        paused = false;                                         // 取消暂停状态(可触发其他弹出)
        mvouttimer = setTimeout(() => {                          // 若主容器不更“新”,则一秒后隐藏主容器
            if (!firstopen) reset();
        }, mvouttimeout * 1000);
    }, false);
    topbox.addEventListener('mousemove', () => {                // 鼠标在主容器中移动时暂停其他计时
        clearTimeout(mvouttimer);                                // 重置隐藏主容器计时
        clearTimeout(popuptimer);                                // 重置弹出计时(避免另一个弹出产生)
        paused = true;                                          // 进入暂停状态(不再开新的弹出计时)
    }, false);

    function texpand(event) {                                   // 弹出主容器,承接鼠标移动事件
        if (!event.shiftKey || event.ctrlKey || event.altKey) { // 如果计时结束时 sfhit 键已弹起,不再弹出
            return;
        }
        let tnode = event.target;
        if (isCursorInElem(topbox, event)) {                    // 如果在主容器内触发,不再弹出
            return;
        }
        if (tnode !== curelem) {                                // 如果目标节点不是前一个节点,重置弹出计时(专注当前节点)
            clearTimeout(popuptimer);
        }
        /* var vnode = getVideoBox(tnode);
        if (!!vnode) {
            expandvideo(vnode);
            return;
        } */
        if (!tnode.textContent && !tnode.src && !window.getComputedStyle(tnode).backgroundImage) {  // 如果目标节点不含文本,不再弹出
            return;
        }
        if (!!tnode.textContent && (tnode.nodeName == "A" || tnode.nodeName == "SPAN")) {     // 如果目标节点为文本链接或节点
            var tnodestyle = window.getComputedStyle(tnode);                // 提取样式
            var fontsize = Number(tnodestyle.fontSize.replace('px', ''));   // 提取样式字体大小
            var linehght = Number(tnodestyle.lineHeight.replace('px', '')); // 提取样式行高
            var pos = getTrueSize(tnode);                                   // 提取节点真实占位
            var lineChrCnt = Math.floor(pos.w / fontsize);
            var lineCnt = Math.ceil(tnode.textContent.length / lineChrCnt);
            var textHeight = linehght * lineCnt * 1.5;
            if (pos.h < textHeight) {
                pos.h = textHeight;
                popuptimer = setTimeout(() => {
                    expandtext(tnode, pos);
                }, popuptimeout * 1000);
            }
        } else if (isOverflow(tnode, event)) {
            popuptimer = setTimeout(() => {
                expandtext(tnode, getTrueSize(tnode));
            }, popuptimeout * 1000);
        } else {
            var pnode = getOverflowPnode(getElemUnderCursor(event), event);
            if (!!pnode) {
                popuptimer = setTimeout(() => {
                    expandtext(pnode, getTrueSize(pnode));
                }, popuptimeout * 1000);
            }
        }


        /*         if (tnode.nodeName == 'DIV' && tnode.className.indexOf('detail_wbtext_') == 0) {
                    btnexpand = tnode.querySelector('span.expand');
                } else if (tnode.nodeName == 'P' && tnode.className == 'txt') {
                    btnexpand = tnode.querySelector('a[action-type="fl_unfold"]');
                } else if (true || !/weibo\.com\/\d{10}\/[a-z0-9A-Z]{9}\??/.test(location.href)) {
                    var tvnode = getVideoBox(tnode, event);
                    //console.log('tvnode159: ', tvnode);
                    if (!!tvnode) {
                        popuptime = setTimeout(() => {
                            expandvideo(tvnode, event);
                        }, popuptimeout * 1000);
                        //console.log("167:", exptime, tvnode);
                    } else {
                        //console.log("181c:", exptime);
                        clearTimeout(popuptime);
                    }
                } */
        curelem = tnode;
    }

    function reset() {
        topbox.classList.remove('shown');

    }

    function expandtext(tnode, posiz) {
        if ((posiz.w * posiz.h) > (window.innerHeight * window.innerWidth * 0.7)) {
            return;
        }
        firstopen = true;
        expbox.innerHTML = tnode.innerHTML;
        var tnodestyle = window.getComputedStyle(tnode);
        var fontsize = Number(tnodestyle.fontSize.replace('px', ''));
        var linehigh = Number(tnodestyle.lineHeight.replace('px', ''));
        var wh = window.innerHeight;
        var ww = window.innerWidth;
        var etop = Math.max(posiz.t - padding, wh * 0.02);
        var eheight = wh * 0.94 - etop;
        var ewidth = Math.max(posiz.w, 120);
        var eleft = Math.max(ww * 0.02, Math.min(ww * 0.98 - posiz.w, posiz.l)) - padding;

        topbox.classList.add('shown');
        expbox.style = `line-height: ${linehigh}px; font-size: ${fontsize}px;`;
        topbox.style = `max-height: ${eheight}px; width: ${ewidth}px; top: ${etop + window.scrollY}px; left: ${eleft}px; `;
    }

    function getVideoBox(elem, event) {
        var velem, telem;
        if (!!elem) {
            if (elem.nodeName == 'VIDEO') {
                velem = elem;
            } else {
                telem = elem.parentNode.querySelector('video');
                if (!!telem && isCursorInElem(telem, event)) {
                    velem = telem;
                }
            }
            return velem;
        }
        return false;
    }

    function expandvideo(vnode) {
        firstopen = true;
        var vbox = creaElemIn('video', expbox);
        vbox.src = vnode.src;
        var etop = window.innerHeight * 0.4;

        topbox.classList.add('shown');
        expbox.style = ``;
        topbox.style = `top: ${etop}px; right: 50px; position: fixed !important;`;
    }

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

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

    function isCursorInElem(elem, event) {
        var x = Number(event.clientX) // 鼠标相对屏幕横坐标
        var y = Number(event.clientY) // 鼠标相对屏幕纵坐标

        var elemLeft = Number(elem.getBoundingClientRect().left) // obj相对屏幕的横坐标
        var elemRight = Number(
            elem.getBoundingClientRect().left + elem.clientWidth
        ) // obj相对屏幕的横坐标+width

        var elemTop = Number(elem.getBoundingClientRect().top) // obj相对屏幕的纵坐标
        var elemBottom = Number(
            elem.getBoundingClientRect().top + elem.clientHeight
        ) // obj相对屏幕的纵坐标+height

        return (x > elemLeft && x < elemRight && y > elemTop && y < elemBottom);
    }

    function getOverflowPnode(elem, event, steps) {
        var l = steps || 0;
        if (isOverflow(elem, event)) {
            return elem;
        } else {
            if (steps > 1) return false;
            var ret = getOverflowPnode(elem.parentNode, event, l++);
            if (!!ret) {
                return ret;
            } else {
                return false;
            }
        }
    }

    function isOverflow(elem, event) {
        if (elem.nodeName !== "#text" && isCursorInElem(elem, event) && window.getComputedStyle(elem).overflow !== "visible") {
            return elem;
        } else {
            return false;
        }
    }

    function getElemUnderCursor(event) {
        const x = event.clientX;
        const y = event.clientY;
        if (document['caretPositionFromPoint']) {
            const pos = document['caretPositionFromPoint'](x, y);
            if (!pos) { return; }
            console.log('pos.offsetNode.parentNode: ', pos.offsetNode.parentNode);
            return pos.offsetNode.parentNode;
        } else {
            return false;
        }
    }

    function getTrueSize(elem, posiz) {
        if (!posiz) {
            var p = elem.getBoundingClientRect();
            posiz = {
                w: p.width,
                h: p.height,
                t: p.top,
                l: p.left,
            };
        }
        var pp = elem.parentNode.getBoundingClientRect();
        var pr = posiz.l + posiz.w, pb = posiz.t + posiz.h;
        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,
            });
        }
    }

})();