前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >写一个chrome插件到底有多难?

写一个chrome插件到底有多难?

原创
作者头像
4cos90
发布2022-12-26 21:48:17
4930
发布2022-12-26 21:48:17
举报
文章被收录于专栏:随想随想

灵感来源

这周刚好看到一个大眼的玩具,感觉非常有意思。但是只能放在自己的网页上又感觉缺乏使用场景。因此我想到能把他翻成chrome插件,注入到平常浏览的网页上,这样使用场景就丰富了。

https://juejin.cn/post/7132409301380890660

开始翻译

chrome插件使用的还是h5一套,因此改动并不算大。首先将代码全部复制下来,echart的源码也下载下来。然后做一些小改动。

首先考虑到我们要把大眼插入进网页,那么首先我们不需要背景色,也不需要一些通配的样式,避免对网页原本的样式造成破坏。

其次是大眼的位置,原本是处于网页居中的位置,在实际浏览中会遮挡住核心的浏览区域,因此我们考虑把大眼移到上方居中。

既然把大眼移动到了上方,那么环视四周的动作也应该加上一点俯视的感觉,这边加上了rotateX(-45deg)。

相应的监听鼠标相对位置的坐标原点也需要改动一下。

最后呢我希望大眼在插入页面后能自动唤醒,不用点击大眼唤醒。改一下也非常的简单。

经过这一些细节的改动,大眼本身就改完了。后面我们把他改成chrome插件。

chrome插件一共三个文件,manifest.json是插件的配置文件,background.js是chrome全局环境执行的,甚至浏览器关闭了也仍然会在后台执行。content_scripts是每个标签页中会注入执行的。可以看到这边把echarts.min.js也一并注入了。

首先是content-script.js,为了避免重复注入,在window里添加了个monitorLoaded的标志位,Init方法中将前面修改完的html和css添加进body中,js部分直接复制即可。

代码语言:javascript
复制
let bigEye;
let eyeball;
let eyeFilter;
let eyeballChart;
let leftRotSize = 0;
let ballSize = 0;
let ballColor = 'transparent'
let rotTimer;
let sleepTimer;
let isSleep = true; // 是否处于休眠状态

if (!window.monitorLoaded) {
    Init();
    window.monitorLoaded = true;
}

function Init() {
    const div = document.createElement("div");
    div.innerHTML = `<div class="eyeSocket eyeSocketSleeping" id='bigEye'>
    <div id="eyeball"></div>
    </div>
    <div class="filter">
    <div class="eyeSocket" id='eyeFilter'>
    </div>
    </div>
    <svg width="0">
    <filter id='filter'>
        <feTurbulence baseFrequency="1">
            <animate id="animate1" attributeName="baseFrequency" dur="1s" from="0.5" to="0.55" begin="0s;animate1.end">
            </animate>
            <animate id="animate2" attributeName="baseFrequency" dur="1s" from="0.55" to="0.5" begin="animate2.end">
            </animate>
        </feTurbulence>
        <feDisplacementMap in="SourceGraphic" scale="50" xChannelSelector="R" yChannelSelector="B" />
    </filter>
    </svg>`;
    document.body.appendChild(div);

    const style = document.createElement('style');
    style.innerHTML = `
        
    body {
        perspective: 1000px;
        --c-eyeSocket: rgb(41, 104, 217);
        --c-eyeSocket-outer: #02ffff;
        --c-eyeSocket-outer-shadow: transparent;
        --c-eyeSocket-inner: rgb(35, 22, 140);
    }
    
    .filter {
        width: 100%;
        height: 100%;
        filter: url('#filter');
    }
    
    .eyeSocket,
    .filter .eyeSocket {
        position: absolute;
        left: calc(50% - 75px);
        top: 17px;
        width: 150px;
        aspect-ratio: 1;
        border-radius: 50%;
        border: 4px solid var(--c-eyeSocket);
        box-shadow: 0px 0px 50px var(--c-eyeSocket-outer-shadow);
        transition: border 0.5s ease-in-out, box-shadow 0.5s ease-in-out;
        z-index: 1000000;
    }
    
    .filter .eyeSocket {
        opacity: 0;
        left: calc(50% - 92px);
        top: 0px;
        transition: all 0.5s ease-in-out;
    }
    
    .eyeSocket::before,
    .eyeSocket::after {
        content: "";
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        border-radius: 50%;
        transition: all 0.5s ease-in-out;
        box-sizing: border-box;
    }
    
    .eyeSocket::before {
        width: calc(100% + 20px);
        height: calc(100% + 20px);
        border: 6px solid var(--c-eyeSocket-outer);
    }
    
    .eyeSocket::after {
        width: 100%;
        height: 100%;
        border: 4px solid var(--c-eyeSocket-inner);
        box-shadow: inset 0px 0px 30px var(--c-eyeSocket-inner);
    }
    
    #eyeball {
        width: 100%;
        height: 100%;
    }
    
    .eyeSocketSleeping {
        animation: sleeping 6s infinite;
    }
    
    .eyeSocketLooking {
        animation: lookAround 2.5s;
    }
    
    @keyframes sleeping {
        0% {
            transform: scale(1);
        }
        50% {
            transform: scale(1.2);
        }
        100% {
            transform: scale(1);
        }
    }
    
    @keyframes lookAround {
        0% {
            transform: translateX(0) rotateY(0);
        }
        10% {
            transform: translateX(0) rotateY(0);
        }
        40% {
            transform: translateX(-70px) rotateX(-45deg) rotateY(-30deg);
        }
        80% {
            transform: translateX(70px) rotateX(-45deg) rotateY(30deg);
        }
        100% {
            transform: translateX(0) rotateY(0);
        }
    }`
    document.body.appendChild(style);

    bigEye = document.getElementById('bigEye');
    eyeball = document.getElementById('eyeball');
    eyeFilter = document.getElementById('eyeFilter');
    eyeballChart = echarts.init(eyeball);
    setTimeout(() => {
        clickToWeakup();
    }, 2000);
    bigEye.addEventListener('click', () => {
        if (!isSleep) return;
        clickToWeakup();
    })
    bigEye.addEventListener('webkitAnimationEnd', () => {
        new Promise(res => {
            clearInterval(rotTimer);
            rotTimer = setInterval(() => {
                getEyeballChart()
                ballSize > 0 && (ballSize -= 0.5);
                leftRotSize === 360 ? (leftRotSize = 0) : (leftRotSize += 0.1);
                if (ballSize === 0) {
                    clearInterval(rotTimer);

                    res();
                }
            }, 10);
        }).then(() => {
            eyeFilter.style.opacity = '0'
            eyeFilter.className = bigEye.className = 'eyeSocket';
            setNormal();
            document.body.addEventListener('mousemove', focusOnMouse);
            rotTimer = setInterval(() => {
                getEyeballChart()
                ballSize <= 12 && (ballSize += 0.1);
                leftRotSize === 360 ? (leftRotSize = 0) : (leftRotSize += 0.1);
            }, 10);
        })
    })

    chrome.runtime.onMessage.addListener(
        function(request, sender, sendResponse) {
            switch (request.type) {
                case "gethref":
                    sendResponse({ href: window.location.href.toLocaleLowerCase() });
                    break;
                case "setAngry":
                    setAngry();
                    break;
                case "setNormal":
                    setNormal();
                    break;
                default:
                    break;
            }
        });

}

// 画眼球
function getEyeballChart() {
    eyeballChart.setOption({
        series: [{
            type: 'gauge',
            radius: '-20%',
            clockwise: false,
            startAngle: `${0 + leftRotSize * 5}`,
            endAngle: `${270 + leftRotSize * 5}`,
            splitNumber: 3,
            detail: false,
            axisLine: {
                show: false,
            },
            axisTick: false,
            splitLine: {
                show: true,
                length: ballSize,
                lineStyle: {
                    shadowBlur: 20,
                    shadowColor: ballColor,
                    shadowOffsetY: '0',
                    color: ballColor,
                    width: 4,
                }
            },
            axisLabel: false
        }, {
            type: 'gauge',
            radius: '-20%',
            clockwise: false,
            startAngle: `${45 + leftRotSize * 5}`,
            endAngle: `${315 + leftRotSize * 5}`,
            splitNumber: 3,
            detail: false,
            axisLine: {
                show: false,
            },
            axisTick: false,
            splitLine: {
                show: true,
                length: ballSize,
                lineStyle: {
                    shadowBlur: 20,
                    shadowColor: ballColor,
                    shadowOffsetY: '0',
                    color: ballColor,
                    width: 4,
                }
            },
            axisLabel: false
        }]
    })
}

// 休眠
function toSleep() {
    isSleep = true;
    clearInterval(rotTimer);
    rotTimer = setInterval(() => {
        getEyeballChart()
        if (ballSize > 0) {
            ballSize -= 0.1;
        } else {
            bigEye.className = 'eyeSocket eyeSocketSleeping'
        }
        leftRotSize === 360 ? (leftRotSize = 0) : (leftRotSize += 0.1);
    }, 10);
    document.body.removeEventListener('mousemove', focusOnMouse);
    bigEye.style.transform = `rotateY(0deg) rotateX(0deg)`;
    eyeball.style.transform = `translate(0px, 0px)`;
}

// 唤醒
function clickToWeakup() {
    isSleep = false;
    eyeFilter.style.opacity = '1'
    eyeFilter.className = bigEye.className = 'eyeSocket eyeSocketLooking'
    setAngry();
    clearInterval(rotTimer);
    rotTimer = setInterval(() => {
        getEyeballChart();
        ballSize <= 50 && (ballSize += 1);
        leftRotSize === 360 ? (leftRotSize = 0) : (leftRotSize += 0.5);
    }, 10);
}
// 生气模式
function setAngry() {
    document.body.style.setProperty('--c-eyeSocket', 'rgb(255,187,255)')
    document.body.style.setProperty('--c-eyeSocket-outer', 'rgb(238,85,135)')
    document.body.style.setProperty('--c-eyeSocket-outer-shadow', 'rgb(255, 60, 86)')
    document.body.style.setProperty('--c-eyeSocket-inner', 'rgb(208,14,74)')
    ballColor = 'rgb(208,14,74)';
}
// 常态模式
function setNormal() {
    document.body.style.setProperty('--c-eyeSocket', 'rgb(41, 104, 217)')
    document.body.style.setProperty('--c-eyeSocket-outer', '#02ffff')
    document.body.style.setProperty('--c-eyeSocket-outer-shadow', 'transparent')
    document.body.style.setProperty('--c-eyeSocket-inner', 'rgb(35, 22, 140)')
    ballColor = 'rgb(0,238,255)';
}

// 关注鼠标
function focusOnMouse(e) {
    // 视口尺寸
    let clientWidth = document.body.clientWidth;
    let clientHeight = document.body.clientHeight;
    // 原点,即bigEye中心位置,页面中心
    let origin = [clientWidth / 2, 0];
    // 鼠标坐标
    let mouseCoords = [e.clientX - origin[0], origin[1] - e.clientY];
    let eyeXDeg = mouseCoords[1] / clientHeight * 80;
    let eyeYDeg = mouseCoords[0] / clientWidth * 60;
    bigEye.style.transform = `rotateY(${eyeYDeg}deg) rotateX(${eyeXDeg}deg)`;
    eyeball.style.transform = `translate(${eyeYDeg / 1.5}px, ${-eyeXDeg / 1.5}px)`;
    // 设置休眠
    if (sleepTimer) clearTimeout(sleepTimer);
    sleepTimer = setTimeout(() => {
        toSleep();
    }, 30000);
}

加入chrome插件间消息的处理,后面会在background中对浏览的页面进行统计,这边先加上了消息处理。可以根据消息返回当前页面url,设置大眼状态。

然后是background.js,chrome.runtime.onInstalled是安装插件的时候,chrome.runtime.onStartup是启动浏览器的时候,chrome.action.onClicked是点击扩展里本插件的图标的时候。 chrome.tabs.sendMessage是给标签页发送消息。第一个参数tab.id即标签页的id,来选择发送消息的目标tab页,第二个参数是消息体,第三个参数是回调方法。 chrome.notifications.create是发送桌面通知。

在background中我监听了当前正在浏览的页面,当开始监听时,会弹出问候语。 之后每隔60秒会检测一次正在访问的页面。如果是正在浏览工作的页面,那么情绪值会上升。如果是在看娱乐页面,那么情绪值会下降。当情绪值>80时,会将大眼设置成angry的状态,并且会弹出一些安慰的话语。同样当情绪值恢复到60以下时,会将大眼设置回normal的状态。当关闭浏览器时,会弹出一个总结的通知。

代码语言:javascript
复制
let logintime;
let emotion;
let worknotice;
let Interval;
let openSign = false;

let type = [
    { href: "baidu", type: "work" },
    { href: "juejin", type: "work" },
    { href: "cloud.tencent", type: "work" },
    { href: "192.168", type: "work" },
    { href: "localhost", type: "work" },
    { href: "csdn", type: "work" },
    { href: "bilibili", type: "fun" },
]

chrome.runtime.onInstalled.addListener(() => {
    start();
    monitor();
});

chrome.runtime.onStartup.addListener(() => {
    start();
    monitor();
});

chrome.action.onClicked.addListener(() => {
    start();
    //     chrome.tabs.executeScript({
    //         file: 'content-script.js',
    //     });
    //     chrome.tabs.executeScript({
    //         file: 'echarts/4.3.0/echarts.min.js',
    //     });
});

function checkWindow() {
    chrome.windows.getAll({}, function(windows) {
        let windowCount = windows.length;
        if (windowCount == 0) {
            close();
        }
    });
}

function monitor() {
    if (!Interval) {
        Interval = setInterval(async() => {
            let tab = await getActiveTab();
            if (tab != null) {
                chrome.tabs.sendMessage(
                    tab.id, { type: "gethref" },
                    (response) => {
                        if (!response) return;
                        let href = response.href;
                        type.forEach(o => {
                            if (href.indexOf(o.href) >= 0) {
                                if (o.type == "work") {
                                    emotion = emotion + 1;
                                }
                                if (o.type == "fun") {
                                    emotion = emotion - 1;
                                }
                                if (emotion > 100) emotion = 100;
                                if (emotion < 0) emotion = 0;
                                if (emotion >= 80) {
                                    chrome.tabs.sendMessage(tab.id, { type: "setAngry" }, () => {});
                                    if (!worknotice) {
                                        let week = logintime.getDay();
                                        if (week == "0" || week == "6") {
                                            chrome.notifications.create(null, {
                                                type: 'basic',
                                                iconUrl: 'icon.png',
                                                title: "太累了!",
                                                message: "周末还加班!你老板真不是人!",
                                            });
                                        } else {
                                            chrome.notifications.create(null, {
                                                type: 'basic',
                                                iconUrl: 'icon.png',
                                                title: "太累了!",
                                                message: "加班辛苦了!休息一下吧!",
                                            });
                                        }
                                        worknotice = true;
                                    }
                                }
                                if (emotion < 60) {
                                    chrome.tabs.sendMessage(tab.id, { type: "setNormal" }, () => {});
                                }
                            }
                        });
                    });
            } else {
                checkWindow();
            }
        }, 60000)
    }
}

function start() {
    logintime = new Date();
    emotion = 50;
    worknotice = false;
    openSign = true;
    chrome.notifications.create(null, {
        type: 'basic',
        iconUrl: 'icon.png',
        title: gettitle(logintime),
        message: getmessage(logintime),
    });
}

function close() {
    if (openSign) {
        if (emotion < 20) {
            chrome.notifications.create(null, {
                type: 'basic',
                iconUrl: 'icon.png',
                title: "这么早?",
                message: "这么早就不玩啦?不多整两把?",
            });
        }
        if (emotion > 80) {
            chrome.notifications.create(null, {
                type: 'basic',
                iconUrl: 'icon.png',
                title: "太累了!",
                message: "活终于干完了!休息一下吧!",
            });
        }
    }
    openSign = false;
}

function gettitle(date) {
    let hour = date.getHours();
    if (hour < 12 && hour > 5) {
        return "早上好!";
    } else if (hour >= 12 && hour < 14) {
        return "中午好!";
    } else if (hour >= 14 && hour < 18) {
        return "下午好";
    } else if (hour >= 18 && hour < 24) {
        return "下午好";
    } else {
        return "滚去睡觉!";
    }
}

function getmessage(date) {
    let fmtlogintime = dateFormat(date, 'yyyy-MM-dd hh:mm:ss');
    //let week = data.getDay();
    let message = `当前时间:${fmtlogintime}`;
    return message;
}

function dateFormat(date, fmt) { // author: meizz
    const o = {
        'M+': date.getMonth() + 1, // 月份
        'd+': date.getDate(), // 日
        'h+': date.getHours(), // 小时
        'm+': date.getMinutes(), // 分
        's+': date.getSeconds(), // 秒
        'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
        'S': date.getMilliseconds() // 毫秒
    };
    if (/(y+)/.test(fmt)) {
        fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
    }
    for (const k in o) {
        if (new RegExp('(' + k + ')').test(fmt)) {
            fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)));
        }
    }
    return fmt;
}


// 获取活跃的 tab,通常是用户正在浏览的页面
async function getActiveTab() {
    return new Promise((resolve) => {
        chrome.tabs.query({
                active: true,
                currentWindow: true,
            },
            (tabs) => {
                if (tabs.length > 0) {
                    resolve(tabs[0]);
                } else {
                    resolve(null);
                }
            }
        );
    });
}

这样一个简单的chrome插件就完成了。还是非常的简单的!

这是我的github地址,需要源码的小伙伴可以直接获取。

https://github.com/4cos90/MonitorChromeEx

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档