网页阅读模式

在任何需要的页面中开启阅读模式,自动或手动选择正文区域。

目前为 2017-01-20 提交的版本。查看 最新版本

// ==UserScript==
// @name                Page Read Mode
// @name:zh-CN          网页阅读模式
// @name:zh-TW          網頁閱讀模式
// @description         Content reader on any page, selecting the text area automatically or manually.
// @description:zh-CN   在任何需要的页面中开启阅读模式,自动或手动选择正文区域。
// @description:zh-TW   在任何需要的頁面中開啟閱讀模式,自動或手動選擇正文區域。

// @authuer             Moshel
// @namespace           https://hzy.pw
// @@homepageURL         https://hzy.pw/p/1364
// @supportURL          https://github.com/h2y/link-fix
// @icon                https://wiki.greasespot.net/images/f/f3/Book.png
// @license             GPL-3.0

// @include             *
// @grant               GM_setClipboard
// @@run-at              context-menu
// @require             https://cdn.staticfile.org/keymaster/1.6.1/keymaster.min.js

// @date                12/17/2015
// @modified            01/20/2016
// @version             1.0.0
// ==/UserScript==


/*
    global var
 */
let mode = 0,        //状态标记
    topNode = null,  //顶层节点
    styleNode = null,
    butNodes = null,
    zoomLevel = 1;


/*
    Tool functions
 */
function isNodeShow(node) {
    const styles = window.getComputedStyle(node);

    if(styles.display=='none' || styles.visibility=='hidden')
        return false;

    if(!parseInt(styles.height) || !parseInt(styles.height))
        return false;

    return true;
}


function nodeStyleInline(node) {
    let styleStr = '',
        styles = window.getComputedStyle(node);

    let keys = Object.keys(styles);
    for(let key of keys) {
        //if(key==='cssText')     continue;
        //if(parseInt(key)==key)  continue;
        /*if(/^(webkit|moz|ms)/.test(key))
            continue;

        if(styles[key]=='')     continue;*/

        let value = styles[key];
        key = changeStrStyle(key.replace('webkit','-webkit'));

        styleStr += key + ':' + value + ';';
    }

    node.className = '';
    node.id = '';
    node.style = styleStr;

    //child
    if(node.childElementCount)
        for(let child of node.children)
            nodeStyleInline(child);
}


// textAlign -> text-align
function changeStrStyle(str) {
    let chars = str.split('');

    for(let i=chars.length-1; i>=0; i--) {
        let ascii = chars[i].charCodeAt(0);
        if(ascii>=65 && ascii<91) {
            //A-Z
            chars[i] = '-' + String.fromCharCode(ascii+32);
        }
    }

    return chars.join('');
}


/*
    main functions
 */
function enterCliping(e) {
    mode = 1;
    e.preventDefault();

    //add style
    if(!styleNode) {
        styleNode = document.createElement('style');
        styleNode.innerHTML = `.cliper-top-node {
            box-shadow: 0 0 20px #777 !important;
            border:     3px solid red !important;
        } .read-mode-reading {
            position:   fixed   !important;
            z-index:    999970  !important;
            top:        0       !important;
            left:       0       !important;
            width:      100%    !important;
            height:     100%    !important;
            background-color: white   !important;
            overflow:         scroll  !important;
            padding:          0       !important;
            border:           0       !important;
            margin:           0       !important;
        } .read-mode-buts {
            position:   fixed;
            z-index:    999985;
            top: 2rem;  right: 1rem;
        } .read-mode-button {
            width:      54px;
            height:     54px;
            margin:     0 .5rem;
            padding:    10px 15px;
            color:      #fff;
            opacity:    .5;
            transition: 500ms;
            border-radius:      5px;
            background-color:   black;
        } .read-mode-button:hover {
            background-color:   white;
            border-radius:      0;
            box-shadow:         0 0 10px #000;
            color:              #000;
        }`;
        styleNode.id = 'read_mode';
        document.body.appendChild(styleNode);
    }

    //choose the init node
    topNode = document.body;
    let preNode = null;

    do {
        preNode = topNode;
        onDown(e);
    }while(preNode!=topNode && preNode.clientHeight*0.9 < topNode.clientHeight);
}

function quitCliping(e) {
    mode = 0;
    e.preventDefault();

    topNode.style.zoom = '';

    changeTopNode(null);

    if(butNodes)
        butNodes.style.display = 'none';

    topNode.classList.remove('read-mode-reading');
}


function buildButNodes() {
    butNodes = document.createElement('div');
    butNodes.className = 'read-mode-buts';

    let buts = [
        {
            text:    "Exit read mode",
            handler: quitCliping,
            icon:    'x'
        }, {
            text:    "Enlarge",
            handler: onEnlarge,
            icon:    '+'
        }, {
            text:    "Shrink",
            handler: onShrink,
            icon:    '-'
        }, {
            text:    "Save HTML data",
            handler: onSaveHTML,
            icon:    '↓'
        }
    ];

    for(let but of buts) {
        let newBut = document.createElement('a');
        newBut.className = 'read-mode-button';
        newBut.innerHTML = but.icon;
        newBut.title     = but.text;
        newBut.onclick   = but.handler;
        butNodes.appendChild(newBut);
    }

    document.body.appendChild(butNodes);
}


function changeTopNode(newNode) {
    if(topNode)
        topNode.classList.remove('cliper-top-node');

    if(newNode)
        newNode.classList.add('cliper-top-node');
    else
        return;

    topNode = newNode;

    //scroll
    var winH = window.screen.availHeight,
        winY = window.scrollY,
        domH = topNode.clientHeight,
        domY = topNode.getBoundingClientRect().top + winY;

    if(domH>winH)
        document.body.scrollTop = domY - 50;
    else
        document.body.scrollTop = domY - (winH-domH)/2;
}


/*
    Event handler
 */
function onEnlarge(e) {
    zoomLevel += .1;
    topNode.style.zoom = zoomLevel;
}
function onShrink(e) {
   zoomLevel -= .1;
   topNode.style.zoom = zoomLevel;
}


function onSaveHTML(e) {
    let htmlStr = '';

    let styleNodes = document.querySelectorAll('style, link[rel=stylesheet]');
    for(let node of styleNodes) {
        if(node.id == 'read_mode')
            continue;

        if(node.nodeName=="LINK")
            htmlStr += `<link rel="stylesheet" href="${node.href}">`;
        else
            htmlStr += node.outerHTML;
    }

    topNode.style.zoom = '';

    //TODO: node filter
    htmlStr += topNode.outerHTML
        .replace(/<style[^>]*>.*?<\/style>/ig, '')
        .replace(/<script[^>]*>.*?<\/script>/ig, '');

    window.GM_setClipboard(htmlStr);

    topNode.style.zoom = zoomLevel;
    alert('Copied into clipboard.');
}


function onUp(e) {
    if(!mode) return;
    e.preventDefault();

    if(topNode.parentElement)
        changeTopNode(topNode.parentNode);
}

function onDown(e) {
    if(!mode) return;
    e.preventDefault();

    if(!topNode.childElementCount)
        return;

    var scanNodes = topNode.children,
        maxNode = null;
    var maxHeight = -1;

    for(let node of scanNodes)
        if(isNodeShow(node) && node.clientHeight > maxHeight) {
            maxHeight = node.clientHeight;
            maxNode = node;
        }

    if(maxNode)
        changeTopNode(maxNode);
}

function onLeft(e) {
    if(!mode) return;
    e.preventDefault();

    let nowNode = topNode;
    for(let node=nowNode; node.previousElementSibling;) {
        node = node.previousElementSibling;
        if(isNodeShow(node)) {
            nowNode = node;
            break;
        }
    }

    if(nowNode!=topNode)
        changeTopNode(nowNode);
    //else: up
    else if (topNode.parentNode) {
        let bakNode = nowNode = topNode;

        onUp(e);
        nowNode = topNode;

        onLeft(e);
        if(nowNode==topNode)
            changeTopNode(bakNode);
        else
            onDown(e);
    }
}

function onRight(e) {
    if(!mode) return;
    e.preventDefault();

    let nowNode = topNode;
    for(let node=nowNode; node.nextElementSibling;) {
        node = node.nextElementSibling;
        if(isNodeShow(node)) {
            nowNode = node;
            break;
        }
    }

    if(nowNode!=topNode)
        changeTopNode(nowNode);
    //else: up
    else if (topNode.parentNode) {
        let bakNode = nowNode = topNode;

        onUp(e);
        nowNode = topNode;

        onRight(e);
        if(nowNode==topNode)
            changeTopNode(bakNode);
        else
            onDown(e);
    }
}


function onEnter(e) {
    if(!mode) return;
    e.preventDefault();

    quitCliping(e);

    topNode.classList.add('read-mode-reading');

    topNode.style.zoom = 1.2;
    zoomLevel = 1.2;

    //buttons
    if(butNodes)
        butNodes.style.display = '';
    else
        buildButNodes();
}


/*
    Main
 */
console.log(window.key);
window.key('alt+r', function(){
	console.log('reading');
	if(mode)      
		quitCliping(new MouseEvent("main"));
	else         
		enterCliping(new MouseEvent("main"));	
});


/*
    bind action
 */
window.key('up', onUp);
window.key('down', onDown);
window.key('left', onLeft);
window.key('right', onRight);

window.key('enter', onEnter);
window.key('esc', quitCliping);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址