给别人的网页上加点东西——通过Chrome扩展为网页添加快速滚动功能

170 阅读3分钟

前言

经常浏览网页的时候,希望能有个快速滚动到顶部或底部的功能。有的网站有这个功能,有的却没有,有的也只实现了回到顶部。而我希望所有的网页都能有回到顶部、底部的功能,这次继续通过WXT开发一个浏览器扩展实现这个功能。

上一篇介绍了如何开发一个简单的扩展,没看过的同学可以回顾下基础内容:如何开发一个chrome扩展

Content Scripts

来看开发网站的介绍,Content Scripts 是一类在网页上下文中运行的文件。它们可以使用标准的DOM接口,实现

  1. 读取浏览器访问的网页的详细信息,比如网页的DOM结构、元素属性等。
  2. 对网页进行修改,比如添加、删除或修改DOM元素,改变网页的样式等。
  3. 将获取到的网页信息传递给它们的父级扩展程序,以便扩展程序可以进一步处理这些信息,实现一些功能。

很明显,我们的需求就需要借助 Content Scripts实现。

WXT 机制

WXT直接在entrypoints/content.ts中编写Content Scripts内容,主要关注两点

export default defineContentScript({
  matches: ['<all_urls>'],
  main(ctx) {  
  },
});
  • matches: 用来匹配网页地址,让脚本内容在哪些网页上运行。比如匹配Google就是 *://*.google.com/*
  • main: 就是真正编写脚本内容的地方

要往页面上添加元素,WXT提供了三种不同的方式:

方式使用函数样式隔离事件隔离HMR访问页面上下文
IntegratedcreateIntegratedUi
Shadow RootcreateShadowRootUi✅ (但默认是关闭的)
IFramecreateIframeUi

我们选择采用 Integrated ,并继续使用React来渲染页面

export default defineContentScript({
  matches: ['<all_urls>'],

  main(ctx) {
    const ui = createIntegratedUi(ctx, {
      position: 'inline',
      anchor: 'body',
      onMount: (container) => {
        const root = ReactDOM.createRoot(container);
        root.render(<Content />);
        return root;
      },
      onRemove: (root) => {
        root.unmount();
      },
    });
    ui.mount();
  },
});

position支持inline、overlay和modal,看一张图就理解

image.png

扩展实现

先思考实现的步骤:

  1. 初始化WXT项目:跟之前一样快速初始化一个WXT功能,并选择React,增加Antd依赖。
  2. 创建Popup页面:用于控制当前页面是否启用滚动功能,并提供快捷键说明。
  3. 监听快捷键:在main函数中增加快捷键监听,实现快捷滚动功能。
  4. 完善Content组件:创建一个React组件,用于在页面上显示滚动到顶部和底部的按钮,并处理鼠标悬停和点击事件。

其中关键的代码示例如下:

要监听快捷键,直接在之前的main函数中增加监听

main(ctx) {
    ctx.addEventListener(document, "keydown", async (e) => {
      // key down event
      if (e.shiftKey) {
        if (e.key === "ArrowDown") {
          if (await WxtStorage.get(CONSTANTS.arrowDownStateKey, true)) {
            UTIL.scrollToBottom();
          }
        } else if (e.key === "ArrowUp") {
          if (await WxtStorage.get(CONSTANTS.arrowUpStateKey, true)) {
            UTIL.scrollToTop();
          }
        }
      }

    });
    ...
}

接下来完善Content组件

function Content() {
    const host = window.location.hostname;

    const [isTopHover, setTopHover] = useState(false);
    const [isBottomHover, setBottomHover] = useState(false);
    const [visible, setVisible] = useState(false);


    useEffect(() => {
        const fetchVisible = async () => {
            const value = await WxtStorage.get(CONSTANTS.hostStatePrefix + host, 1);
            setVisible(value === undefined ? true : value! === 1);
        };

        fetchVisible();
    }, []);

    const baseStyle = {
        width: '30px',
        height: '30px',
        padding: '10px',
        margin: '5px',
        borderRadius: '10px',
        cursor: 'pointer',
    }
    const scrollToTopStyle = {
        ...baseStyle,
        opacity: isTopHover ? 1 : 0.2,
        backgroundColor: isTopHover ? '#efefef' : 'transparent',
    }
    const scrollToBottomStyle = {
        ...baseStyle,
        opacity: isBottomHover ? 1 : 0.2,
        backgroundColor: isBottomHover ? '#efefef' : 'transparent',
    }
    return (
        <div style={{ position: 'fixed', top: '50%', right: '10px', transform: 'translate(0, -50%)' }}>
            <div style={{ visibility: visible ? 'visible' : 'hidden' }}>
                <img style={scrollToTopStyle} onClick={UTIL.scrollToTop} onMouseEnter={() => setTopHover(true)} onMouseLeave={() => setTopHover(false)} src={TopImg} alt='Go to top' />
            </div>
            <div style={{ visibility: visible ? 'visible' : 'hidden' }}>
                <img style={scrollToBottomStyle} onClick={UTIL.scrollToBottom} onMouseEnter={() => setBottomHover(true)} onMouseLeave={() => setBottomHover(false)} src={BottomImg} alt='Go to bottom' />
            </div>
        </div>
    );
}

完整代码见: github.com/xckevin/chr… 最终效果图如下

image.png