- 星座运势: 天聚数行API集成,5维进度条+幸运标签+今日概述 - AI资讯: 天聚数行API,图文布局5条展示,文件缓存2小时刷新 - 知识卡片: AI生成,关键字+提示词配置,30分钟刷新 - 桌面设置: 独立WebView2窗口,760x1350,含壁纸/布局/城市/颜色等配置 - 显示控制: 壁纸/时间/天气/星座/知识/AI资讯独立开关,秒显示开关 - 文件缓存: 星座运势+AI资讯缓存到本地,启动即显示上次数据 - initDone防抖: 防止设置窗口初始化触发卡片重载
240 lines
6.7 KiB
HTML
240 lines
6.7 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Dynamic Wallpaper</title>
|
|
<style>
|
|
* { margin: 0; padding: 0; }
|
|
html, body { width: 100%; height: 100%; overflow: hidden; background: #000; }
|
|
canvas { display: block; width: 100vw; height: 100vh; }
|
|
#info {
|
|
position: fixed; top: 20px; right: 20px;
|
|
color: rgba(255,255,255,0.6); font: 14px monospace;
|
|
pointer-events: none; user-select: none;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<canvas id="c"></canvas>
|
|
<div id="info"></div>
|
|
|
|
<script>
|
|
const canvas = document.getElementById('c');
|
|
const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
|
|
if (!gl) { document.body.innerHTML = '<h1 style="color:#fff;text-align:center;margin-top:40vh">WebGL 不可用</h1>'; }
|
|
|
|
let paused = false;
|
|
let fullscreen = false;
|
|
let mouseX = 0, mouseY = 0;
|
|
let lastMove = 0;
|
|
|
|
// --- 状态控制 (Go Bridge) ---
|
|
window.setPaused = function(v) { paused = v; };
|
|
window.setFullscreen = function(v) { fullscreen = v; };
|
|
|
|
// --- 鼠标 ---
|
|
document.addEventListener('mousemove', e => {
|
|
mouseX = e.clientX / window.innerWidth;
|
|
mouseY = 1.0 - e.clientY / window.innerHeight;
|
|
lastMove = performance.now();
|
|
});
|
|
document.addEventListener('click', e => {
|
|
// 点击涟漪效果 — 传给 shader
|
|
clickX = e.clientX / window.innerWidth;
|
|
clickY = 1.0 - e.clientY / window.innerHeight;
|
|
clickTime = performance.now();
|
|
});
|
|
|
|
let clickX = 0, clickY = 0, clickTime = 0;
|
|
|
|
// --- Shader ---
|
|
const vertSrc = `
|
|
attribute vec2 a_pos;
|
|
void main() { gl_Position = vec4(a_pos, 0.0, 1.0); }
|
|
`;
|
|
|
|
// 极光 + 流体噪声 shader
|
|
const fragSrc = `
|
|
precision highp float;
|
|
uniform float u_time;
|
|
uniform vec2 u_resolution;
|
|
uniform vec2 u_mouse;
|
|
uniform float u_click;
|
|
uniform vec2 u_clickPos;
|
|
|
|
// simplex-like noise
|
|
vec3 mod289(vec3 x) { return x - floor(x * (1.0/289.0)) * 289.0; }
|
|
vec2 mod289(vec2 x) { return x - floor(x * (1.0/289.0)) * 289.0; }
|
|
vec3 permute(vec3 x) { return mod289(((x*34.0)+1.0)*x); }
|
|
|
|
float snoise(vec2 v) {
|
|
const vec4 C = vec4(0.211324865405187, 0.366025403784439,
|
|
-0.577350269189626, 0.024390243902439);
|
|
vec2 i = floor(v + dot(v, C.yy));
|
|
vec2 x0 = v - i + dot(i, C.xx);
|
|
vec2 i1;
|
|
i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
|
|
vec4 x12 = x0.xyxy + C.xxzz;
|
|
x12.xy -= i1;
|
|
i = mod289(i);
|
|
vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0));
|
|
vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0);
|
|
m = m*m; m = m*m;
|
|
vec3 x = 2.0 * fract(p * C.www) - 1.0;
|
|
vec3 h = abs(x) - 0.5;
|
|
vec3 ox = floor(x + 0.5);
|
|
vec3 a0 = x - ox;
|
|
m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);
|
|
vec3 g;
|
|
g.x = a0.x * x0.x + h.x * x0.y;
|
|
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
|
|
return 130.0 * dot(m, g);
|
|
}
|
|
|
|
void main() {
|
|
vec2 uv = gl_FragCoord.xy / u_resolution;
|
|
vec2 p = uv * 2.0 - 1.0;
|
|
p.x *= u_resolution.x / u_resolution.y;
|
|
|
|
float t = u_time * 0.3;
|
|
|
|
// 鼠标影响
|
|
vec2 mp = u_mouse * 2.0 - 1.0;
|
|
mp.x *= u_resolution.x / u_resolution.y;
|
|
float mouseDist = length(p - mp);
|
|
float mouseInfluence = smoothstep(0.8, 0.0, mouseDist) * 0.3;
|
|
|
|
// 点击涟漪
|
|
float ripple = 0.0;
|
|
if (u_click > 0.0) {
|
|
vec2 cp = u_clickPos * 2.0 - 1.0;
|
|
cp.x *= u_resolution.x / u_resolution.y;
|
|
float d = length(p - cp);
|
|
ripple = sin(d * 30.0 - u_click * 8.0) * exp(-d * 3.0) * exp(-u_click * 2.0) * 0.15;
|
|
}
|
|
|
|
// 多层噪声
|
|
float n1 = snoise(p * 1.5 + vec2(t * 0.5, t * 0.3)) * 0.5 + 0.5;
|
|
float n2 = snoise(p * 3.0 + vec2(-t * 0.7, t * 0.5)) * 0.5 + 0.5;
|
|
float n3 = snoise(p * 0.8 + vec2(t * 0.2, -t * 0.4) + mouseInfluence) * 0.5 + 0.5;
|
|
|
|
// 极光色彩
|
|
vec3 c1 = vec3(0.05, 0.2, 0.4); // 深蓝
|
|
vec3 c2 = vec3(0.0, 0.8, 0.5); // 翠绿
|
|
vec3 c3 = vec3(0.3, 0.1, 0.6); // 紫色
|
|
vec3 c4 = vec3(0.1, 0.5, 0.9); // 天蓝
|
|
|
|
// 极光带
|
|
float aurora = smoothstep(0.3, 0.7, n3) * smoothstep(0.8, 0.4, n1);
|
|
vec3 auroraColor = mix(c2, c4, n2) * aurora * 1.2;
|
|
|
|
// 底层渐变
|
|
vec3 bg = mix(c1, c3, uv.y * 0.5 + n1 * 0.3);
|
|
bg += auroraColor;
|
|
|
|
// 星星效果
|
|
float stars = pow(snoise(p * 20.0), 12.0) * 0.8;
|
|
bg += vec3(stars);
|
|
|
|
// 涟漪叠加
|
|
bg += vec3(ripple * 2.0, ripple * 3.0, ripple * 4.0);
|
|
|
|
// 鼠标光晕
|
|
bg += vec3(0.1, 0.3, 0.5) * mouseInfluence;
|
|
|
|
// 轻微暗角
|
|
float vignette = 1.0 - smoothstep(0.5, 1.5, length(p * 0.7));
|
|
bg *= vignette * 0.9 + 0.1;
|
|
|
|
gl_FragColor = vec4(bg, 1.0);
|
|
}
|
|
`;
|
|
|
|
// --- 编译 Shader ---
|
|
function createShader(type, src) {
|
|
const s = gl.createShader(type);
|
|
gl.shaderSource(s, src);
|
|
gl.compileShader(s);
|
|
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
|
|
console.error('Shader error:', gl.getShaderInfoLog(s));
|
|
return null;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
const vs = createShader(gl.VERTEX_SHADER, vertSrc);
|
|
const fs = createShader(gl.FRAGMENT_SHADER, fragSrc);
|
|
const prog = gl.createProgram();
|
|
gl.attachShader(prog, vs);
|
|
gl.attachShader(prog, fs);
|
|
gl.linkProgram(prog);
|
|
gl.useProgram(prog);
|
|
|
|
// 全屏四边形
|
|
const buf = gl.createBuffer();
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
|
|
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 1,-1, -1,1, 1,1]), gl.STATIC_DRAW);
|
|
const aPos = gl.getAttribLocation(prog, 'a_pos');
|
|
gl.enableVertexAttribArray(aPos);
|
|
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);
|
|
|
|
// Uniforms
|
|
const uTime = gl.getUniformLocation(prog, 'u_time');
|
|
const uRes = gl.getUniformLocation(prog, 'u_resolution');
|
|
const uMouse = gl.getUniformLocation(prog, 'u_mouse');
|
|
const uClick = gl.getUniformLocation(prog, 'u_click');
|
|
const uClickPos = gl.getUniformLocation(prog, 'u_clickPos');
|
|
|
|
// --- 渲染循环 ---
|
|
let lastFrame = 0;
|
|
let targetFPS = 30;
|
|
let currentFPS = 30;
|
|
|
|
function resize() {
|
|
const dpr = window.devicePixelRatio || 1;
|
|
canvas.width = window.innerWidth * dpr;
|
|
canvas.height = window.innerHeight * dpr;
|
|
gl.viewport(0, 0, canvas.width, canvas.height);
|
|
}
|
|
window.addEventListener('resize', resize);
|
|
resize();
|
|
|
|
function render(now) {
|
|
requestAnimationFrame(render);
|
|
if (paused || fullscreen) return;
|
|
|
|
// 帧率控制
|
|
const interval = 1000 / targetFPS;
|
|
if (now - lastFrame < interval) return;
|
|
lastFrame = now;
|
|
|
|
// 自适应帧率:鼠标静止 5s 后降帧
|
|
const idleTime = now - lastMove;
|
|
if (idleTime > 5000) {
|
|
targetFPS = 10;
|
|
} else {
|
|
targetFPS = 30;
|
|
}
|
|
|
|
const t = now / 1000.0;
|
|
const clickElapsed = clickTime > 0 ? (now - clickTime) / 1000.0 : 0.0;
|
|
|
|
gl.uniform1f(uTime, t);
|
|
gl.uniform2f(uRes, canvas.width, canvas.height);
|
|
gl.uniform2f(uMouse, mouseX, mouseY);
|
|
gl.uniform1f(uClick, clickElapsed > 3.0 ? 0.0 : clickElapsed);
|
|
gl.uniform2f(uClickPos, clickX, clickY);
|
|
|
|
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
}
|
|
|
|
requestAnimationFrame(render);
|
|
|
|
// 通知 Go 层壁纸就绪
|
|
if (window.wallpaperReady) {
|
|
wallpaperReady().then(() => console.log('wallpaper embedded'));
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|