CSDN也能有星星拖尾(星星拖尾浏览器扩展的实现,内附完整代码和具体实现流程)

在之前的文章中【用JS实现星星拖尾效果(文章末尾附带完整代码)-CSDN博客】,我分享了基于 DOM 的星星拖尾效果实现,得到了不少反馈。很多朋友希望这个效果能更灵活地应用到任意网页,而不是局限在特定页面。于是,我决定将它升级为一个浏览器扩展,并转向 Canvas 技术,以提升性能和视觉表现。这篇文章将聚焦扩展的开发过程,分享从 DOM 到 Canvas 的转变原因、实现细节,以及如何让星光拖尾适配所有网页。

CSDN也能有星星拖尾(星星拖尾浏览器扩展的实现,内附完整代码和具体实现流程)_第1张图片

1. 为什么要做成浏览器扩展?

星星拖尾效果最初是为单个页面设计的,但实际使用场景中,用户希望在浏览任何网页时都能看到这种炫酷的光标效果,比如在社交媒体、博客甚至视频网站上。为此,将效果封装成浏览器扩展是理想选择,原因如下:

  • 跨页面适用:通过 Chrome 扩展的 content_scripts 机制,可以在任何网页()注入脚本和样式,实现即插即用的效果。
  • 用户友好:扩展提供开关控制,用户可随时启用或禁用效果,无需修改网页代码。
  • 一致体验:无论页面内容如何,拖尾效果都能保持一致的视觉风格,提升个性化体验。

目标是让用户安装扩展后,鼠标移动时就能在任意网页触发彩色星星拖尾,带来沉浸式的交互感。

2. 从 DOM 到 Canvas:解决性能瓶颈

在之前的 DOM 实现中,我通过创建大量 div 元素模拟星星,配合 CSS 动画实现淡出效果。但在实际测试中,尤其是在视频网站(bilibili)或动态页面上,频繁的 DOM 操作带来了明显问题:

  • 性能卡顿:现代网页效果炫酷,视频网站常包含高频渲染的元素(如视频播放器)。创建和移除大量 DOM 元素会增加浏览器负担,导致帧率下降,拖尾显得卡顿。
  • 内存占用:DOM 元素数量多时,内存占用激增,尤其在长时间浏览或多标签页场景下,性能问题更明显。
  • 动画限制:CSS 动画虽然简单,但难以实现更加复杂的星星效果。

这些困难促使我转向 Canvas 技术。Canvas 的优势在于:

  • 高性能渲染:Canvas 通过 2D 上下文直接绘制像素,单次绘制即可呈现复杂图形,无需管理多个 DOM 元素,适合高频更新场景。
  • 灵活性:可以轻松绘制五角星、控制旋转和透明度,满足炫酷效果的需求。
  • 现代化需求:如今的网页(如视频、游戏网站)需要流畅的动态效果,Canvas 能与 GPU 加速结合,减少卡顿,提供丝滑体验。
3. 实现星星拖尾扩展的挑战

将效果转为扩展并使用 Canvas,面临以下挑战:

  • 适配所有网页:不同网页的 z-index、布局和事件监听可能干扰效果,需确保星星层级最高且不影响交互。
  • 动态窗口支持:网页大小可能随时变化,Canvas 需自适应窗口尺寸。
  • 性能与视觉平衡:在保证星星数量和动画流畅的前提下,需避免过度绘制导致 CPU 占用过高。
  • 扩展架构:需合理配置 manifest.json,确保脚本和样式正确注入,同时保持代码模块化。
4. 如何实现 Canvas 星星拖尾扩展

以下是实现的核心步骤,结合代码讲解如何解决上述挑战:

4.1 扩展配置:跨页面注入

通过 manifest.json,我定义了扩展的基本信息和注入规则:

{
  "manifest_version": 3,
  "name": "Cursor Shape - Star Trail Effect",
  "version": "1.0",
  "description": "Add beautiful star trail effects to your cursor movement on any webpage",
  "permissions": ["activeTab"],
  "content_scripts": [
    {
      "matches": [""],
      "css": ["styles.css"],
      "js": ["content.js"]
    }
  ],
  "icons": {
    "128": "icon.svg"
  }
} 
  • matches: [""] 确保效果在所有网页生效。
  • content_scripts 注入 styles.css(定义动画)和 content.js(核心逻辑)。
4.2 初始化 Canvas 画布

在 content.js 中,我创建了一个全屏 Canvas,确保星星绘制不被页面内容遮挡:

const canvas = document.createElement('canvas');
canvas.style.cssText = 'position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 999999;';
document.body.appendChild(canvas);

与 DOM 版本类似,我通过固定步长控制星星生成频率,避免密集轨迹:

4.4 控制星星生成

function shouldSpawnStar(distance) {
    accumulatedDistance += distance;
    const maxVariation = spawnFrequency * frequencyVariation;
    const currentThreshold = spawnFrequency + Math.random() * maxVariation;
    if (accumulatedDistance >= currentThreshold) {
        accumulatedDistance = 0;
        return true;
    }
    return false;
}

  • pointer-events: none 避免 Canvas 干扰网页交互。
  • z-index: 999999 保证星星显示在最上层。
  • 监听 resize 事件动态调整 Canvas 尺寸:
    function resizeCanvas() {
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;
    }
    window.addEventListener('resize', resizeCanvas);
    4.3 星星对象与动画逻辑

    为了管理星星,我定义了一个 Star 类,包含位置、尺寸、颜色、角度、速度等属性,并处理更新和绘制:

    class Star {
        constructor(x, y, size, color, angle, speed) {
            this.x = x;
            this.y = y;
            this.size = size;
            this.color = color;
            this.angle = angle;
            this.speed = speed;
            this.alpha = 1;
            this.rotation = Math.random() * (rotationRange[1] - rotationRange[0]) + rotationRange[0];
        }
        update() {
            this.x += Math.cos(this.angle) * this.speed;
            this.y += Math.sin(this.angle) * this.speed;
            this.alpha *= fadeSpeed;
            this.rotation += 2;
            return this.alpha > 0.1;
        }
        draw() {
            ctx.save();
            ctx.translate(this.x, this.y);
            ctx.rotate(this.rotation * Math.PI / 180);
            ctx.beginPath();
            for (let i = 0; i < 5; i++) {
                const angle = (i * 4 * Math.PI) / 5 - Math.PI / 2;
                const x = Math.cos(angle) * this.size;
                const y = Math.sin(angle) * this.size;
                if (i === 0) {
                    ctx.moveTo(x, y);
                } else {
                    ctx.lineTo(x, y);
                }
            }
            ctx.closePath();
            ctx.fillStyle = this.color;
            ctx.globalAlpha = this.alpha;
            ctx.fill();
            ctx.restore();
        }
    }

  • 更新:调整位置、透明度(fadeSpeed: 0.98)和旋转角度。
  • 绘制:使用 Canvas 路径绘制五角星,动态旋转和淡出。
  • 鼠标移动距离超过阈值(spawnFrequency: 10)时,生成新星星。
  • 随机偏移(calculateSpreadVector)增加轨迹自然感:

 function calculateSpreadVector(dx, dy) {
    const angle = Math.atan2(dy, dx);
    const direction = Math.random() < 0.5 ? -1 : 1;
    const spreadAngle = (Math.random() * Math.PI / 3) * direction;
    const finalAngle = angle + spreadAngle;
    const speed = Math.sqrt(dx * dx + dy * dy) * 0.05;
    return { angle: finalAngle, speed };
}

4.5 动画循环与性能优化

使用 requestAnimationFrame 驱动动画,每帧清空画布并更新星星:

function animate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    stars = stars.filter(star => {
        const isAlive = star.update();
        if (isAlive) star.draw();
        return isAlive;
    });
    requestAnimationFrame(animate);
}

  • 性能优化:限制最大星星数量(maxStars: 50),移除透明度低于 0.1 的星星。
  • 鼠标状态:通过 isMouseMoving 判断鼠标是否静止,避免不必要计算。
4.6 CSS 动画辅助

虽然主要逻辑在 Canvas 中,styles.css 定义了淡出动画,供未来扩展(如混合 DOM 效果):

 @keyframes fadeOut {
    0% { opacity: 1; transform: scale(1) translate(0, 0) rotate(0deg); }
    100% { opacity: 0; transform: scale(0.5) translate(var(--moveX), var(--moveY)) rotate(var(--rotate)); }
}

5. 最终效果与心得

这个扩展成功实现了目标:

  • 视觉表现:鼠标移动时,彩色五角星沿路径散射,伴随旋转和淡出,效果流畅自然,适合视频网站等动态页面。
  • 性能:Canvas 渲染高效,50 颗星星的限制确保低 CPU 占用。
  • 通用性:扩展支持所有网页,安装后即用,无需额外配置。

完整项目结构如下: 

 CursorShape/
  ├── manifest.json
  ├── styles.css
  ├── content.js
  └── icon.png

 manifest.json:

{
  "manifest_version": 3,
  "name": "Cursor Shape - Star Trail Effect",
  "version": "1.0",
  "description": "Add beautiful star trail effects to your cursor movement on any webpage",
  "permissions": ["activeTab"],
  "content_scripts": [
    {
      "matches": [""],
      "css": ["styles.css"],
      "js": ["content.js"]
    }
  ],
  "icons": {
    "128": "icon.svg"
  }
} 

styles.css:

@keyframes fadeOut {
    0% {
        opacity: 1;
        transform: scale(1) translate(0, 0) rotate(0deg);
    }
    100% {
        opacity: 0;
        transform: scale(0.5) translate(var(--moveX), var(--moveY)) rotate(var(--rotate));
    }
}

.cursor-star {
    position: fixed;
    pointer-events: none;
    will-change: transform, opacity;
    z-index: 999999;
    /* 使用GPU加速 */
    transform: translateZ(0);
    backface-visibility: hidden;
    perspective: 1000px;
}

.cursor-star.animate {
    animation: fadeOut var(--duration) var(--timing) forwards;
}

.cursor-star-five {
    clip-path: polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%);
}

.cursor-star-glow {
    filter: drop-shadow(0 0 4px currentColor);
} 

content.js:

function createStarsTrail({
    maxStars = 60,
    colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEEAD', '#D4A5A5', '#9B59B6', '#3498DB', '#E67E22', '#2ECC71'],
    baseSizeRange = [8, 15],
    maxDistance = 100,
    spawnFrequency = 15,
    frequencyVariation = 0.6,
    rotationRange = [-45, 45],
    fadeSpeed = 0.95
} = {}) {
    // 创建canvas元素
    const canvas = document.createElement('canvas');
    canvas.style.cssText = 'position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 999999;';
    document.body.appendChild(canvas);
    
    // 获取canvas上下文
    const ctx = canvas.getContext('2d');
    
    // 设置canvas尺寸为窗口大小
    function resizeCanvas() {
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;
    }
    
    // 初始化尺寸
    resizeCanvas();
    window.addEventListener('resize', resizeCanvas);

    // 星星类
    class Star {
        constructor(x, y, size, color, angle, speed) {
            this.x = x;
            this.y = y;
            this.size = size;
            this.color = color;
            this.angle = angle;
            this.speed = speed;
            this.alpha = 1;
            this.rotation = Math.random() * (rotationRange[1] - rotationRange[0]) + rotationRange[0];
        }

        update() {
            // 更新位置
            this.x += Math.cos(this.angle) * this.speed;
            this.y += Math.sin(this.angle) * this.speed;
            
            // 更新透明度
            this.alpha *= fadeSpeed;
            
            // 更新旋转
            this.rotation += 2;
            
            return this.alpha > 0.1;
        }

        draw() {
            ctx.save();
            ctx.translate(this.x, this.y);
            ctx.rotate(this.rotation * Math.PI / 180);
            
            // 设置透明度
            ctx.globalAlpha = this.alpha;
            
            // 绘制五角星
            ctx.beginPath();
            for (let i = 0; i < 5; i++) {
                const angle = (i * 4 * Math.PI) / 5 - Math.PI / 2;
                const x = Math.cos(angle) * this.size;
                const y = Math.sin(angle) * this.size;
                if (i === 0) {
                    ctx.moveTo(x, y);
                } else {
                    ctx.lineTo(x, y);
                }
            }
            ctx.closePath();
            
            // 填充颜色
            ctx.fillStyle = this.color;
            ctx.fill();
            
            ctx.restore();
        }
    }

    let stars = [];
    let lastX = 0;
    let lastY = 0;
    let mouseX = 0;
    let mouseY = 0;
    let accumulatedDistance = 0;
    let isMouseMoving = false;

    // 计算星星大小
    function calculateStarSize() {
        return Math.random() * (baseSizeRange[1] - baseSizeRange[0]) + baseSizeRange[0];
    }

    // 计算扩散向量
    function calculateSpreadVector(dx, dy) {
        const angle = Math.atan2(dy, dx);
        const direction = Math.random() < 0.5 ? -1 : 1;
        const spreadAngle = (Math.random() * Math.PI / 3) * direction;
        const finalAngle = angle + spreadAngle;
        const speed = Math.sqrt(dx * dx + dy * dy) * 0.05;
        
        return {
            angle: finalAngle,
            speed: speed
        };
    }

    // 判断是否应该生成新星星
    function shouldSpawnStar(distance) {
        accumulatedDistance += distance;
        const maxVariation = spawnFrequency * frequencyVariation;
        const currentThreshold = spawnFrequency + Math.random() * maxVariation;
        
        if (accumulatedDistance >= currentThreshold) {
            accumulatedDistance = 0;
            return true;
        }
        return false;
    }

    // 动画循环
    function animate() {
        // 清除画布
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        
        // 更新和绘制所有星星
        stars = stars.filter(star => {
            const isAlive = star.update();
            if (isAlive) {
                star.draw();
            }
            return isAlive;
        });
        
        // 如果鼠标在移动,生成新星星
        if (isMouseMoving) {
            const dx = mouseX - lastX;
            const dy = mouseY - lastY;
            const distance = Math.sqrt(dx * dx + dy * dy);
            
            if (shouldSpawnStar(distance)) {
                const size = calculateStarSize();
                const { angle, speed } = calculateSpreadVector(dx, dy);
                const color = colors[Math.floor(Math.random() * colors.length)];
                
                stars.push(new Star(mouseX, mouseY, size, color, angle, speed));
                
                // 限制最大星星数量
                if (stars.length > maxStars) {
                    stars.shift();
                }
            }
            
            lastX = mouseX;
            lastY = mouseY;
        }
        
        requestAnimationFrame(animate);
    }

    // 启动动画
    animate();

    // 处理鼠标移动
    const handleMouseMove = (event) => {
        mouseX = event.clientX;
        mouseY = event.clientY;
        isMouseMoving = true;
        
        clearTimeout(window.mouseMoveTimeout);
        window.mouseMoveTimeout = setTimeout(() => {
            isMouseMoving = false;
        }, 100);
    };

    document.addEventListener('mousemove', handleMouseMove);

    // 返回清理函数
    return {
        destroy: () => {
            document.removeEventListener('mousemove', handleMouseMove);
            window.removeEventListener('resize', resizeCanvas);
            clearTimeout(window.mouseMoveTimeout);
            if (canvas && canvas.parentNode) {
                canvas.remove();
            }
            stars = [];
        }
    };
}

// 初始化星星效果
const starsTrail = createStarsTrail({
    maxStars: 50,
    colors: ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEEAD', '#D4A5A5', '#9B59B6', '#3498DB', '#E67E22', '#2ECC71'],
    baseSizeRange: [5, 12],
    maxDistance: 100,
    spawnFrequency: 10,
    frequencyVariation: 0.5,
    rotationRange: [-45, 45],
    fadeSpeed: 0.98
});

icon去找一个自己喜欢的就可以了,记得可能需要修改JSON里的这部分代码:

  "icons": {"128": "icon.svg"  }

在浏览器中导入扩展的方式:

edge浏览器可进入这个链接:edge://extensions/

CSDN也能有星星拖尾(星星拖尾浏览器扩展的实现,内附完整代码和具体实现流程)_第2张图片

然后依次进行操作:
1)进入开发者模式
2)加载解压缩的扩展
3)导入这个文件夹并打开扩展

你可能感兴趣的:(前端,json,javascript,css,edge浏览器)