<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">简易屏幕保护</title>
<style>
body {
margin: 0;
overflow: hidden;
background-color: black;
font-family: Arial, Helvetica, sans-serif;
}
#canvas-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
cursor: none; /* 隐藏鼠标指针,更像屏保 */
}
#info {
position: fixed;
bottom: 20px;
left: 0;
width: 100%;
text-align: center;
color: rgba(255, 255, 255, 0.2);
font-size: 14px;
pointer-events: none;
user-select: none;
z-index: 10;
}
@media (max-width: 600px) {
#info {
font-size: 10px;
bottom: 10px;
}
}
</style>
</head>
<body>
<div id="canvas-container">
<canvas id="screenSaverCanvas"></canvas>
</div>
<div id="info">点击或移动鼠标退出 · 简易屏保</div>
<script>
(function() {
// ----- 获取 Canvas 和上下文 -----
const canvas = document.getElementById('screenSaverCanvas');
const ctx = canvas.getContext('2d');
// ----- 尺寸变量 -----
let width = window.innerWidth;
let height = window.innerHeight;
// ----- 粒子系统参数 -----
const PARTICLE_COUNT = 80; // 粒子数量
const CONNECTION_DISTANCE = 150; // 连线最大距离 (像素)
const PARTICLE_RADIUS = 2.5; // 粒子半径
const SPEED = 1.2; // 移动速度因子
// ----- 颜色配置 (柔和的蓝色/紫色调) -----
const COLORS = [
{ r: 100, g: 180, b: 255 }, // 淡蓝
{ r: 150, g: 120, b: 255 }, // 淡紫
{ r: 80, g: 220, b: 200 }, // 青绿
{ r: 255, g: 150, b: 200 } // 淡粉
];
// ----- 粒子数组 -----
let particles = [];
// ----- 鼠标 / 触摸交互 (用于退出屏保) -----
let interactionTimeout = null;
const EXIT_DELAY = 1000; // 无操作后重新启动 (实际上通过点击/移动立即退出,这里用于重启逻辑)
// ----- 控制变量 -----
let isRunning = true; // 控制动画循环
let animationFrameId = null;
// ----- 退出屏保:通过重置状态或跳转? 这里我们设计为点击或移动鼠标时,停止动画并显示一个简单的退出效果,但作为屏保脚本,我们更倾向于“退出屏保模式”回到正常页面。
// 但因为我们没有实际的后台,我们实现为:当检测到交互,清空画布并显示一条消息,然后可以重新开始。
// 为了让演示更友好,我们设计为:第一次交互停止动画并显示“已退出”,再次点击重新激活。
// 但更符合屏保习惯:任何交互都停止屏保,显示空白或提示,直到用户再次点击“激活”。
// 我们实现一个切换功能:点击/移动鼠标 切换 运行/停止。
// 但是为了模仿真实屏保,一般移动鼠标或点击会立即退出到桌面,这里我们简化为停止动画并显示文字。
// 为了不干扰展示,如果停止后3秒无操作,自动重新启动。
// 我们采用更直接的方式:默认运行,一旦检测到鼠标移动或点击,立即停止动画并显示“屏保已退出”,
// 然后过2秒后自动重新启动,如果再次交互,再次停止,如此循环。
// 这样做可以完整展示屏保效果,同时演示退出机制。
// ----- 初始化粒子 -----
function initParticles() {
particles = [];
for (let i = 0; i < PARTICLE_COUNT; i++) {
particles.push({
x: Math.random() * width,
y: Math.random() * height,
vx: (Math.random() - 0.5) * SPEED * 2,
vy: (Math.random() - 0.5) * SPEED * 2,
color: COLORS[Math.floor(Math.random() * COLORS.length)],
radius: PARTICLE_RADIUS * (0.8 + Math.random() * 0.6) // 随机大小
});
}
}
// ----- 更新粒子位置 (边界反弹) -----
function updateParticles() {
for (let p of particles) {
p.x += p.vx;
p.y += p.vy;
// 边界反弹 (并稍微随机化速度防止周期性)
if (p.x < 0) {
p.x = 0;
p.vx = -p.vx * (0.9 + Math.random() * 0.2);
} else if (p.x > width) {
p.x = width;
p.vx = -p.vx * (0.9 + Math.random() * 0.2);
}
if (p.y < 0) {
p.y = 0;
p.vy = -p.vy * (0.9 + Math.random() * 0.2);
} else if (p.y > height) {
p.y = height;
p.vy = -p.vy * (0.9 + Math.random() * 0.2);
}
// 限制速度范围,避免过快
const maxSpeed = SPEED * 2.5;
const speedVal = Math.hypot(p.vx, p.vy);
if (speedVal > maxSpeed) {
p.vx = (p.vx / speedVal) * maxSpeed;
p.vy = (p.vy / speedVal) * maxSpeed;
}
}
}
// ----- 绘制粒子及连线 -----
function draw() {
ctx.clearRect(0, 0, width, height);
// 1. 绘制连线 (先绘制连线,让粒子在上一层)
for (let i = 0; i < particles.length; i++) {
for (let j = i + 1; j < particles.length; j++) {
const p1 = particles[i];
const p2 = particles[j];
const dx = p1.x - p2.x;
const dy = p1.y - p2.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < CONNECTION_DISTANCE) {
// 根据距离调整透明度
const opacity = 1 - (dist / CONNECTION_DISTANCE);
// 混合颜色 (取两个粒子的颜色平均)
const avgR = (p1.color.r + p2.color.r) / 2;
const avgG = (p1.color.g + p2.color.g) / 2;
const avgB = (p1.color.b + p2.color.b) / 2;
ctx.strokeStyle = `rgba(${avgR | 0}, ${avgG | 0}, ${avgB | 0}, ${opacity * 0.6})`;
ctx.lineWidth = 1.2;
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.stroke();
}
}
}
// 2. 绘制粒子
for (let p of particles) {
ctx.beginPath();
ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
// 粒子颜色 (稍微带点发光效果)
ctx.fillStyle = `rgba(${p.color.r}, ${p.color.g}, ${p.color.b}, 0.9)`;
ctx.fill();
// 增加一点光晕
ctx.shadowColor = `rgba(${p.color.r}, ${p.color.g}, ${p.color.b}, 0.5)`;
ctx.shadowBlur = 8;
ctx.fill();
ctx.shadowBlur = 0; // 重置避免影响其他绘制
}
}
// ----- 动画循环 -----
function animate() {
if (!isRunning) {
// 如果停止,则不更新画面,但可以绘制一个退出提示
// 但为了干净,我们会在停止时清空画布并显示文字,所以这里直接返回
return;
}
updateParticles();
draw();
animationFrameId = requestAnimationFrame(animate);
}
// ----- 启动动画 -----
function startAnimation() {
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
}
isRunning = true;
// 重新初始化粒子以适应新尺寸 (如果尺寸变了)
initParticles();
animate();
// 隐藏任何覆盖提示 (如果有)
const infoEl = document.getElementById('info');
infoEl.style.color = 'rgba(255, 255, 255, 0.2)';
infoEl.textContent = '点击或移动鼠标退出 · 简易屏保';
}
// ----- 停止动画 (退出屏保) -----
function stopAnimation() {
if (!isRunning) return;
isRunning = false;
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
}
// 清空画布并显示退出文字
ctx.clearRect(0, 0, width, height);
// 绘制一些淡出的粒子痕迹 (可选) 或者直接显示文字
ctx.fillStyle = 'rgba(0, 0, 0, 1)';
ctx.fillRect(0, 0, width, height);
// 显示退出文字
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = 'rgba(200, 200, 255, 0.3)';
ctx.font = '24px Arial, sans-serif';
ctx.fillText('屏幕保护已退出', width/2, height/2 - 20);
ctx.fillStyle = 'rgba(200, 200, 255, 0.15)';
ctx.font = '16px Arial, sans-serif';
ctx.fillText('点击或移动鼠标重新启动', width/2, height/2 + 30);
// 更新底部信息
const infoEl = document.getElementById('info');
infoEl.style.color = 'rgba(255, 255, 255, 0.4)';
infoEl.textContent = '点击或移动鼠标重新启动屏保';
}
// ----- 切换状态 (交互触发) -----
function toggleScreenSaver() {
if (isRunning) {
stopAnimation();
// 设置一个定时器,如果一段时间无操作自动重启 (但为了演示,我们让用户通过交互重启)
// 清除之前的定时器
if (interactionTimeout) {
clearTimeout(interactionTimeout);
interactionTimeout = null;
}
// 设置5秒后自动重启 (如果仍处于停止状态)
interactionTimeout = setTimeout(() => {
if (!isRunning) {
startAnimation();
// 更新底部信息
const infoEl = document.getElementById('info');
infoEl.style.color = 'rgba(255, 255, 255, 0.2)';
infoEl.textContent = '点击或移动鼠标退出 · 简易屏保';
}
}, 5000);
} else {
// 如果当前停止,则重新启动
if (interactionTimeout) {
clearTimeout(interactionTimeout);
interactionTimeout = null;
}
startAnimation();
}
}
// ----- 事件监听:鼠标移动、点击、触摸 -----
function handleInteraction(event) {
// 阻止默认行为,避免页面滚动等
event.preventDefault();
toggleScreenSaver();
}
// 鼠标移动 (使用节流,避免过于频繁)
let moveThrottle = null;
function handleMouseMove(event) {
if (moveThrottle) return;
moveThrottle = setTimeout(() => {
moveThrottle = null;
}, 300); // 300ms 内只触发一次
handleInteraction(event);
}
// 鼠标点击
function handleMouseClick(event) {
handleInteraction(event);
}
// 触摸事件 (移动端)
function handleTouch(event) {
handleInteraction(event);
}
// ----- 窗口大小自适应 -----
function handleResize() {
width = window.innerWidth;
height = window.innerHeight;
canvas.width = width;
canvas.height = height;
// 如果正在运行,重新初始化粒子以适应新尺寸
if (isRunning) {
initParticles();
} else {
// 如果停止,重绘停止画面
ctx.fillStyle = 'rgba(0, 0, 0, 1)';
ctx.fillRect(0, 0, width, height);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = 'rgba(200, 200, 255, 0.3)';
ctx.font = '24px Arial, sans-serif';
ctx.fillText('屏幕保护已退出', width/2, height/2 - 20);
ctx.fillStyle = 'rgba(200, 200, 255, 0.15)';
ctx.font = '16px Arial, sans-serif';
ctx.fillText('点击或移动鼠标重新启动', width/2, height/2 + 30);
}
}
// ----- 初始化 Canvas 尺寸和粒子 -----
function setup() {
canvas.width = width;
canvas.height = height;
initParticles();
// 默认启动
startAnimation();
// 注册事件
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('click', handleMouseClick);
document.addEventListener('touchstart', handleTouch, { passive: false });
window.addEventListener('resize', handleResize);
}
// ----- 启动一切 -----
setup();
// ----- 清理 (理论上脚本执行期间不会卸载,但提供良好习惯) -----
// 如果需要清理,可以导出清理函数,但这里不涉及。
})();
</script>
</body>
</html>
屏保机制与交互
您可以通过鼠标或触屏与这个动态粒子屏保进行互动,它的核心逻辑围绕粒子运动与视觉连接展开。

- 动态粒子网络:屏幕上有数十个带有柔和色彩的粒子随机运动,当粒子之间的距离小于设定值时,它们之间会绘制出半透明的连线,形成不断变化的网状结构。
- 交互与状态切换:移动鼠标、点击或触摸屏幕会触发屏保的“退出”与“重启”,首次交互会停止动画并显示退出提示;再次交互或等待约5秒,屏保会自动恢复运行。
- 自适应与视觉:页面会自适应窗口大小,粒子在碰到屏幕边界时会反弹,并随机微调速度,使其运动更自然,整体采用深色背景与低饱和度的彩色粒子,视觉上较为舒适。
优化建议: 您可以根据喜好调整 PARTICLE_COUNT(粒子数量)、SPEED(运动速度)和 CONNECTION_DISTANCE(连线距离)等参数,以改变屏保的视觉效果和密集度。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播