惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Know Your Adversary
Know Your Adversary
博客园 - 【当耐特】
C
Check Point Blog
Recorded Future
Recorded Future
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
WordPress大学
WordPress大学
小众软件
小众软件
T
The Blog of Author Tim Ferriss
Blog — PlanetScale
Blog — PlanetScale
GbyAI
GbyAI
博客园 - 叶小钗
阮一峰的网络日志
阮一峰的网络日志
云风的 BLOG
云风的 BLOG
宝玉的分享
宝玉的分享
The Cloudflare Blog
U
Unit 42
Google DeepMind News
Google DeepMind News
博客园 - Franky
The Register - Security
The Register - Security
Hugging Face - Blog
Hugging Face - Blog
F
Full Disclosure
Microsoft Azure Blog
Microsoft Azure Blog
I
InfoQ
博客园 - 三生石上(FineUI控件)
Y
Y Combinator Blog
F
Fortinet All Blogs
IT之家
IT之家
V
Visual Studio Blog
H
Heimdal Security Blog
K
Kaspersky official blog
S
Schneier on Security
C
CXSECURITY Database RSS Feed - CXSecurity.com
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
CERT Recently Published Vulnerability Notes
有赞技术团队
有赞技术团队
美团技术团队
The Hacker News
The Hacker News
Google Online Security Blog
Google Online Security Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
A
About on SuperTechFans
Schneier on Security
Schneier on Security
Jina AI
Jina AI
SecWiki News
SecWiki News
www.infosecurity-magazine.com
www.infosecurity-magazine.com
M
MIT News - Artificial intelligence
G
GRAHAM CLULEY
L
LangChain Blog
S
Secure Thoughts

Hyde Blog

Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog 关于我 关于我 Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog Hyde Blog
Hyde Blog
Hyde · 2025-10-05 · via Hyde Blog

背景彩带 ​

简介 ​

这是一个基于 Canvas 实现的动态渐变彩带背景效果,专为 VitePress 主题设计的 Vue Composition API 组件,主要特点包括:

🌈 流动渐变 :通过三角函数计算实现彩虹色渐变效果

🎨 视觉可定制 :支持透明度、彩带宽度、层级等样式配置

📱 响应式 :自动适配屏幕尺寸和高 DPI 显示

🖱️ 交互支持 :可配置点击重绘功能,鼠标每次点击,随机生成彩带背景。

新建文件 ​

docs\.vitepress\theme\hooks\文件中新建useRibbon.ts文件

ts

// 背景彩带
import { isClient, useMounted, useScopeDispose } from "vitepress-theme-teek";

interface UseRibbonOptions {
  /**
   * 透明度
   *
   * @default 0.6
   */
  alpha?: number;
  /**
   * 宽度
   *
   * @default 90
   */
  size?: number;
  /**
   * z-index
   *
   * @default -1
   */
  zIndex?: number;
  /**
   * 是否立即执行彩带渲染
   *
   * @default true
   */
  immediate?: boolean;
  /**
   * 点击页面时是否重新渲染彩带
   *
   * @default false
   */
  clickReRender?: boolean;
  /**
   * 是否为彩带元素绑定点击事件,仅限 clickReRender 为 true 和 zIndex >= 0 时触发。
   * 1、当为 true,则给彩带元素绑定点击事件
   * 2、当为 false,则给全局 document 绑定点击事件
   *
   * @default false
   */
  ribbonDomBindClick?: boolean;
}

const fn = () => {};

export const useRibbon = (options: UseRibbonOptions = {}) => {
  let canvas: HTMLCanvasElement | null = null;
  let ctx: CanvasRenderingContext2D | null = null;
  let cleanupFn = fn;

  const {
    alpha = 0.6,
    size = 90,
    zIndex = -1,
    clickReRender = false,
    ribbonDomBindClick = false,
    immediate = true,
  } = options;

  const initRibbon = () => {
    if (!isClient) return fn;
    if (document.getElementById("ribbon")) return fn;

    // 创建 canvas
    canvas = document.createElement("canvas");
    canvas.id = "ribbon";
    canvas.style.cssText = `position:fixed;top:0;left:0;z-index:${zIndex}`;
    document.body.append(canvas);

    const dpr = window.devicePixelRatio || 1;
    const width = window.innerWidth;
    const height = window.innerHeight;

    const math = Math;
    let r = 0;
    const PI_2 = math.PI * 2;
    const cos = math.cos;
    const random = math.random;

    canvas.width = width * dpr;
    canvas.height = height * dpr;
    ctx = canvas.getContext("2d");

    if (!ctx) return fn;

    ctx.scale(dpr, dpr);
    ctx.globalAlpha = alpha;

    let path: { x: number; y: number }[] = [];

    function init() {
      if (!ctx) return fn;
      ctx.clearRect(0, 0, width, height);
      path = [
        { x: 0, y: height * 0.7 + size },
        { x: 0, y: height * 0.7 - size },
      ];
      while (path[1].x < width + size) {
        draw(path[0], path[1]);
      }
    }

    function draw(
      start: { x: number; y: number },
      end: { x: number; y: number }
    ) {
      if (!ctx) return fn;
      ctx.beginPath();
      ctx.moveTo(start.x, start.y);
      ctx.lineTo(end.x, end.y);
      const nextX = end.x + (random() * 2 - 0.25) * size;
      const nextY = geneY(end.y);
      ctx.lineTo(nextX, nextY);
      ctx.closePath();

      r -= PI_2 / -50;
      ctx.fillStyle =
        "#" +
        (
          ((cos(r) * 127 + 128) << 16) |
          ((cos(r + PI_2 / 3) * 127 + 128) << 8) |
          (cos(r + (PI_2 / 3) * 2) * 127 + 128)
        ).toString(16);
      ctx.fill();
      path[0] = path[1];
      path[1] = { x: nextX, y: nextY };
    }

    function geneY(y: number): number {
      const temp = y + (random() * 2 - 1.1) * size;
      return temp > height || temp < 0 ? geneY(y) : temp;
    }

    init();

    // 点击重新绘制
    const handleClick = () => init();

    const dom = ribbonDomBindClick ? canvas : document;

    if (clickReRender) {
      dom.addEventListener("click", handleClick);
      dom.addEventListener("touchstart", handleClick, { passive: true });
    } // 返回清理函数
    return () => {
      if (clickReRender) {
        dom.removeEventListener("click", handleClick);
        dom.removeEventListener("touchstart", handleClick);
      }
      if (canvas && canvas.parentNode) {
        canvas.parentNode.removeChild(canvas);
      }
      canvas = null;
      ctx = null;
    };
  };

  const start = () => {
    cleanupFn = initRibbon();
  };

  const stop = () => {
    cleanupFn();
  };

  useMounted(() => {
    if (immediate) start();
  });

  useScopeDispose(stop);

  return { start, stop };
};

注册使用 ​

docs\.vitepress\theme\components\TeekLayoutProvider.vue中注册使用

提示

  • 如果想禁用鼠标点击随机生成彩带,可以设置clickReRenderfalse
  • 如果不想在文章页显示彩带可以删除const { start: startRibbon, stop: stopRibbon } =

vue

<script setup lang="ts" name="TeekLayoutProvider">
import { useRibbon } from "../hooks/useRibbon"; //导入彩带背景

// 彩带背景
const { start: startRibbon, stop: stopRibbon } = useRibbon({
  immediate: false,
  clickReRender: true,
}); // 点击页面重新渲染彩带
</script>

<template></template>

效果 ​

HTML 中使用 ​

html

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />

    <style>
      canvas {
        position: absolute;
        top: 0;
        left: 0;
        z-index: 0;
        width: 100%;
        height: 100%;
        pointer-events: none;
      }
    </style>
  </head>

  <body>
    <canvas></canvas>

    <script>
      document.addEventListener("touchmove", function (e) {
        e.preventDefault();
      });
      var c = document.getElementsByTagName("canvas")[0],
        x = c.getContext("2d"),
        pr = window.devicePixelRatio || 1,
        w = window.innerWidth,
        h = window.innerHeight,
        f = 90,
        q,
        m = Math,
        r = 0,
        u = m.PI * 2,
        v = m.cos,
        z = m.random;
      c.width = w * pr;
      c.height = h * pr;
      x.scale(pr, pr);
      x.globalAlpha = 0.6;
      function i() {
        x.clearRect(0, 0, w, h);
        q = [
          { x: 0, y: h * 0.7 + f },
          { x: 0, y: h * 0.7 - f },
        ];
        while (q[1].x < w + f) d(q[0], q[1]);
      }
      function d(i, j) {
        x.beginPath();
        x.moveTo(i.x, i.y);
        x.lineTo(j.x, j.y);
        var k = j.x + (z() * 2 - 0.25) * f,
          n = y(j.y);
        x.lineTo(k, n);
        x.closePath();
        r -= u / -50;
        x.fillStyle =
          "#" +
          (
            ((v(r) * 127 + 128) << 16) |
            ((v(r + u / 3) * 127 + 128) << 8) |
            (v(r + (u / 3) * 2) * 127 + 128)
          ).toString(16);
        x.fill();
        q[0] = q[1];
        q[1] = { x: k, y: n };
      }
      function y(p) {
        var t = p + (z() * 2 - 1.1) * f;
        return t > h || t < 0 ? y(p) : t;
      }
      document.onclick = i;
      document.ontouchstart = i;
      i();
    </script>
  </body>
</html>