Files
nexus-terminal/doc/custom_html_theme/丝带.html
T
2025-05-27 21:36:06 +08:00

164 lines
6.5 KiB
HTML

<style>
#color-ribbons-container {
width: 100%; height: 100%;
background-color: #222733; /* 柔和的深蓝灰色 */
overflow: hidden; position: relative;
}
#ribbons-canvas { display: block; position: absolute; top: 0; left: 0; }
</style>
<div id="color-ribbons-container">
<canvas id="ribbons-canvas"></canvas>
</div>
<script>
(function() {
const canvas = document.getElementById('ribbons-canvas');
const layerElement = canvas.closest('.terminal-custom-html-layer') || canvas.parentElement;
if (!canvas || !layerElement) return;
const ctx = canvas.getContext('2d');
let width, height, animationFrameId;
const ribbons = [];
const numRibbons = 5; // 彩带数量
let time = 0;
// HSL颜色辅助函数
function hslToRgb(h, s, l) {
let r, g, b;
if (s == 0) {
r = g = b = l; // achromatic
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
class Ribbon {
constructor() {
this.numPoints = 50 + Math.floor(Math.random() * 30); // 组成彩带路径的点数
this.points = [];
this.baseY = height * (0.2 + Math.random() * 0.6);
this.amplitude = height * (0.05 + Math.random() * 0.15);
this.frequency = 0.002 + Math.random() * 0.003;
this.phase = Math.random() * Math.PI * 2;
this.thickness = 20 + Math.random() * 30; // 彩带厚度
this.speed = 0.5 + Math.random() * 0.5; // 彩带横向移动速度
this.hueStart = Math.random(); // 0-1
this.hueChangeSpeed = 0.0005 + Math.random() * 0.001;
this.opacity = 0.1 + Math.random() * 0.2;
this.xOffset = -width * Math.random(); // 初始横向偏移,让彩带从不同位置进入
}
update(deltaTime) {
this.xOffset += this.speed * (deltaTime / (1000/60)); // Normalize speed to 60fps
if (this.xOffset > width + this.thickness * 5) { // 彩带完全移出右侧
this.xOffset = -this.thickness * 5 - Math.random() * width * 0.5; // 从左侧重新进入
this.baseY = height * (0.2 + Math.random() * 0.6); // 改变Y基线
this.hueStart = Math.random(); // 改变颜色
}
this.hueStart = (this.hueStart + this.hueChangeSpeed) % 1;
this.points = [];
for (let i = 0; i < this.numPoints; i++) {
const progress = i / (this.numPoints - 1); // 0 to 1
const x = this.xOffset + progress * (width + this.thickness * 10); // 彩带绘制范围比屏幕宽
const y = this.baseY + Math.sin(x * this.frequency + this.phase + time * 0.01) * this.amplitude +
Math.cos(x * this.frequency * 0.5 + this.phase * 0.7 + time * 0.005) * this.amplitude * 0.5;
this.points.push({ x, y });
}
}
draw() {
if (this.points.length < 2) return;
ctx.beginPath();
for (let i = 0; i < this.points.length -1; i++) {
const p1 = this.points[i];
const p2 = this.points[i+1];
// 计算垂直于路径的偏移点,形成彩带的上下边缘
const angle = Math.atan2(p2.y - p1.y, p2.x - p1.x);
const dxTop = Math.sin(angle) * this.thickness / 2;
const dyTop = -Math.cos(angle) * this.thickness / 2;
const dxBottom = -Math.sin(angle) * this.thickness / 2;
const dyBottom = Math.cos(angle) * this.thickness / 2;
if (i === 0) {
ctx.moveTo(p1.x + dxTop, p1.y + dyTop);
} else {
ctx.lineTo(p1.x + dxTop, p1.y + dyTop);
}
}
// 绘制下边缘 (反向)
for (let i = this.points.length - 1; i > 0; i--) {
const p1 = this.points[i];
// p2 is points[i-1] but we use p1's angle approx or recalculate
const p_prev = this.points[i-1];
const angle = Math.atan2(p1.y - p_prev.y, p1.x - p_prev.x); // Angle from prev to current
const dxBottom = -Math.sin(angle) * this.thickness / 2;
const dyBottom = Math.cos(angle) * this.thickness / 2;
ctx.lineTo(p1.x + dxBottom, p1.y + dyBottom);
}
ctx.closePath();
const [r,g,b] = hslToRgb(this.hueStart, 0.7, 0.6); // (饱和度0.7, 亮度0.6)
ctx.fillStyle = `rgba(${r},${g},${b}, ${this.opacity})`;
ctx.fill();
}
}
function init() {
width = layerElement.offsetWidth;
height = layerElement.offsetHeight;
canvas.width = width;
canvas.height = height;
ribbons.length = 0;
for (let i = 0; i < numRibbons; i++) {
ribbons.push(new Ribbon());
}
// 使用 'lighter' 或 'screen' 混合模式让彩带叠加时颜色混合更好
ctx.globalCompositeOperation = 'screen';
}
let lastTime = 0;
function animate(currentTime) {
const deltaTime = currentTime - lastTime;
lastTime = currentTime;
time++;
ctx.clearRect(0, 0, width, height); // 清晰的背景,或者用低alpha填充制造拖尾
ribbons.forEach(ribbon => {
ribbon.update(deltaTime || (1000/60)); // Provide a fallback for deltaTime on first frame
ribbon.draw();
});
animationFrameId = requestAnimationFrame(animate);
}
let resizeTimeout;
function onResize() {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
if (animationFrameId) cancelAnimationFrame(animationFrameId);
init();
// animate(0); // Restart animation loop
}, 250);
}
init();
animate(0); // Start animation loop
window.addEventListener('resize', onResize);
canvas.cleanup = function() { if (animationFrameId) cancelAnimationFrame(animationFrameId); };
})();
</script>