fix: 修复升级后整站无样式与点击特效失效

- 旧哈希静态资源随版本升级被删除,被缓存的 HTML 仍请求旧文件名导致
  FileNotFoundException 500,补 compat/assets 历史资源并在构建时合并
- postbuild 增加静态资源引用校验,缺失即构建失败
- ClickEffect 改为常驻配置节点 + data 属性驱动,避免配置未落库时静默失效;
  强制回流修正 transition 不触发;特效层挂到 body 下避免被导航栏遮挡
- 新增 scripts: postbuild/snapshot-assets/package/smoke-clickfx
- 版本 1.2.1
This commit is contained in:
nxxy335 committed 2026-09-27 16:27:51 +08:00
1 parent a127d5a314
commit 125d0592c9
42 files changed
+951 -123

No files matched your search

+203 -117
View File
@@ -1,14 +1,35 @@
---
/*
* 点击特效组件。
*
* 设计要点:容器节点始终输出,由脚本读取 data-enabled / data-effect 决定行为。
* 旧写法用 th:if 依赖配置值严格等于布尔 true,一旦配置未落库、值不是布尔,
* 或 Thymeleaf 求值把属性移除,节点就不会渲染 —— 特效静默失效且无任何报错。
*
* 特效元素统一挂到 body 下的 .wi-click-fx-layer:
* .wi-main 是独立的层叠上下文(z-index:1),留在里面会被导航栏/页脚遮住。
*
* spawn() 里必须强制回流,否则新节点的初态与终态在同一帧被合并,
* transition 不会触发,表现同样是"点了没反应"。
*/
---
<div id="wi-click-effect" th:if="${theme.config?.style?.click_effect_enabled == true}" th:attr="data-effect=${theme.config?.style?.click_effect_type}">
<div
id="wi-click-effect"
hidden
th:attr="data-effect=${theme.config?.style?.click_effect_type ?: 'values'},data-enabled=${theme.config?.style?.click_effect_enabled}"
></div>
<script is:inline th:inline="none">
(function () {
var container = document.getElementById("wi-click-effect");
if (!container) return;
var effect = container.getAttribute("data-effect") || "off";
if (effect === "off" || !effect) return;
(function () {
var cfg = document.getElementById("wi-click-effect");
if (!cfg) return;
if ((cfg.getAttribute("data-enabled") || "").trim() !== "true") return;
var effect = (cfg.getAttribute("data-effect") || "").trim();
if (!effect) effect = "values";
if (effect === "off") return;
/* 社会主义核心价值观 */
var CORE_VALUES = [
@@ -23,23 +44,47 @@
"#51cf66", "#94d82d", "#fcc419", "#ff922b"
];
/* 创建单个 DOM 元素并加入舞台 */
function pick(list) {
return list[Math.floor(Math.random() * list.length)];
}
/*
* 特效层统一挂在 body 下:
* .wi-main 自身是层叠上下文(z-index:1),留在里面会被导航栏/页脚遮住;
* 同时该层 position:fixed 且铺满视口,内部 fixed 子元素的
* left/top 仍等价于 clientX/clientY。
*/
var layer = document.createElement("div");
layer.className = "wi-click-fx-layer";
document.body.appendChild(layer);
/* 创建单个 DOM 元素并加入特效层,返回后即可设置终态样式 */
function spawn(el, x, y) {
el.style.left = x + "px";
el.style.top = y + "px";
container.appendChild(el);
layer.appendChild(el);
/*
* 强制回流:新插入的元素若在同一帧内被改写 transform/opacity,
* 浏览器会把初态与终态合并,transition 不会触发 —— 表现为"没有任何效果"。
*/
void el.offsetWidth;
return el;
}
function removeLater(el, delay) {
setTimeout(function () {
if (el.parentNode) el.parentNode.removeChild(el);
}, delay);
}
/* ========== 1. 社会主义核心价值观 ========== */
function spawnValueText(x, y) {
var el = document.createElement("span");
el.className = "wi-click-fx wi-click-fx--text";
el.textContent = CORE_VALUES[Math.floor(Math.random() * CORE_VALUES.length)];
el.style.color = RANDOM_COLORS[Math.floor(Math.random() * RANDOM_COLORS.length)];
el.textContent = pick(CORE_VALUES);
el.style.color = pick(RANDOM_COLORS);
spawn(el, x, y);
setTimeout(function () {
if (el.parentNode) el.parentNode.removeChild(el);
}, 1200);
removeLater(el, 1300);
}
/* ========== 2. 随机彩色爱心 ========== */
@@ -47,89 +92,61 @@
var el = document.createElement("span");
el.className = "wi-click-fx wi-click-fx--heart";
el.innerHTML = "&#10084;";
el.style.color = RANDOM_COLORS[Math.floor(Math.random() * RANDOM_COLORS.length)];
var dx = (Math.random() - 0.5) * 80;
el.style.color = pick(RANDOM_COLORS);
spawn(el, x, y);
requestAnimationFrame(function () {
el.style.transform = "translate(" + dx + "px, -80px) scale(0.2)";
el.style.opacity = "0";
});
setTimeout(function () {
if (el.parentNode) el.parentNode.removeChild(el);
}, 1000);
el.style.transform = "translate(calc(-50% + " + ((Math.random() - 0.5) * 80).toFixed(1) + "px), -90px) scale(0.3)";
el.style.opacity = "0";
removeLater(el, 1100);
}
/* ========== 3. 烟花 ========== */
function spawnFirework(x, y) {
var count = 16;
var color = RANDOM_COLORS[Math.floor(Math.random() * RANDOM_COLORS.length)];
var count = 18;
var color = pick(RANDOM_COLORS);
for (var i = 0; i < count; i++) {
var el = document.createElement("span");
el.className = "wi-click-fx wi-click-fx--spark";
el.style.background = color;
el.style.boxShadow = "0 0 6px " + color;
var angle = (Math.PI * 2 * i) / count;
var dist = 40 + Math.random() * 30;
var dx = Math.cos(angle) * dist;
var dy = Math.sin(angle) * dist;
var dist = 40 + Math.random() * 34;
spawn(el, x, y);
requestAnimationFrame(function (e, tx, ty) {
return function () {
e.style.transform = "translate(" + tx + "px, " + ty + "px) scale(0)";
e.style.opacity = "0";
};
}(el, dx, dy));
(function (e) {
setTimeout(function () {
if (e.parentNode) e.parentNode.removeChild(e);
}, 800);
})(el);
el.style.transform = "translate(calc(-50% + " + (Math.cos(angle) * dist).toFixed(1) + "px), calc(-50% + " + (Math.sin(angle) * dist).toFixed(1) + "px)) scale(0)";
el.style.opacity = "0";
removeLater(el, 900);
}
}
/* ========== 4. 烟花波纹 ========== */
function spawnRipple(x, y) {
var color = RANDOM_COLORS[Math.floor(Math.random() * RANDOM_COLORS.length)];
var color = pick(RANDOM_COLORS);
for (var r = 0; r < 2; r++) {
var el = document.createElement("span");
el.className = "wi-click-fx wi-click-fx--ripple";
el.style.borderColor = color;
spawn(el, x, y);
(function (e, delay) {
(function (el, delay) {
setTimeout(function () {
requestAnimationFrame(function () {
e.style.transform = "translate(-50%, -50%) scale(4)";
e.style.opacity = "0";
});
el.style.transform = "translate(-50%, -50%) scale(4)";
el.style.opacity = "0";
removeLater(el, 700);
}, delay);
setTimeout(function () {
if (e.parentNode) e.parentNode.removeChild(e);
}, delay + 700);
})(el, r * 150);
})(el, r * 160);
removeLater(el, 160 + 900);
}
}
/* ========== 5. 粒子 ========== */
function spawnParticles(x, y, n) {
n = n || 8;
n = n || 10;
for (var i = 0; i < n; i++) {
var el = document.createElement("span");
el.className = "wi-click-fx wi-click-fx--particle";
el.style.background = RANDOM_COLORS[Math.floor(Math.random() * RANDOM_COLORS.length)];
var dx = (Math.random() - 0.5) * 60;
var dy = (Math.random() - 0.5) * 60 - 20;
el.style.background = pick(RANDOM_COLORS);
spawn(el, x, y);
requestAnimationFrame(function (e, tx, ty) {
return function () {
e.style.transform = "translate(" + tx + "px, " + ty + "px) scale(0)";
e.style.opacity = "0";
};
}(el, dx, dy));
(function (e) {
setTimeout(function () {
if (e.parentNode) e.parentNode.removeChild(e);
}, 700);
})(el);
el.style.transform = "translate(calc(-50% + " + ((Math.random() - 0.5) * 60).toFixed(1) + "px), calc(-50% + " + ((Math.random() - 0.5) * 60 - 20).toFixed(1) + "px)) scale(0)";
el.style.opacity = "0";
removeLater(el, 800);
}
}
@@ -138,17 +155,11 @@
var el = document.createElement("span");
el.className = "wi-click-fx wi-click-fx--star";
el.innerHTML = "&#9733;";
el.style.color = RANDOM_COLORS[Math.floor(Math.random() * RANDOM_COLORS.length)];
var dx = (Math.random() - 0.5) * 50;
var dy = (Math.random() - 0.5) * 50 - 30;
el.style.color = pick(RANDOM_COLORS);
spawn(el, x, y);
requestAnimationFrame(function () {
el.style.transform = "translate(" + dx + "px, " + dy + "px) scale(1.6)";
el.style.opacity = "0";
});
setTimeout(function () {
if (el.parentNode) el.parentNode.removeChild(el);
}, 800);
el.style.transform = "translate(calc(-50% + " + ((Math.random() - 0.5) * 50).toFixed(1) + "px), calc(-50% + " + ((Math.random() - 0.5) * 50 - 30).toFixed(1) + "px)) scale(1.6)";
el.style.opacity = "0";
removeLater(el, 900);
}
/* 点击分发 */
@@ -162,26 +173,28 @@
};
/* 拖尾效果 */
function spawnTrailStar(x, y) {
var el = document.createElement("span");
el.className = "wi-click-fx wi-click-fx--trail-star";
el.innerHTML = "&#9733;";
el.style.color = pick(RANDOM_COLORS);
spawn(el, x, y);
el.style.opacity = "0";
removeLater(el, 700);
}
function spawnTrailParticle(x, y) {
var el = document.createElement("span");
el.className = "wi-click-fx wi-click-fx--trail-particle";
el.style.background = pick(RANDOM_COLORS);
spawn(el, x, y);
el.style.opacity = "0";
removeLater(el, 600);
}
var trailHandlers = {
"star-trail": function (x, y) {
var el = document.createElement("span");
el.className = "wi-click-fx wi-click-fx--trail-star";
el.innerHTML = "&#9733;";
el.style.color = RANDOM_COLORS[Math.floor(Math.random() * RANDOM_COLORS.length)];
spawn(el, x, y);
setTimeout(function () {
if (el.parentNode) el.parentNode.removeChild(el);
}, 600);
},
"particle-trail": function (x, y) {
var el = document.createElement("span");
el.className = "wi-click-fx wi-click-fx--trail-particle";
el.style.background = RANDOM_COLORS[Math.floor(Math.random() * RANDOM_COLORS.length)];
spawn(el, x, y);
setTimeout(function () {
if (el.parentNode) el.parentNode.removeChild(el);
}, 500);
}
"star-trail": spawnTrailStar,
"particle-trail": spawnTrailParticle
};
var isTrail = effect.indexOf("trail") !== -1;
@@ -189,55 +202,128 @@
if (isTrail) {
var handler = trailHandlers[effect];
if (!handler) return;
var lastX = 0, lastY = 0;
var throttle = false;
document.addEventListener("mousemove", function (e) {
lastX = e.clientX;
lastY = e.clientY;
if (throttle) return;
throttle = true;
requestAnimationFrame(function () {
throttle = false;
handler(lastX, lastY);
});
}, { passive: true });
var lastX = 0;
var lastY = 0;
var throttling = false;
document.addEventListener(
"mousemove",
function (e) {
lastX = e.clientX;
lastY = e.clientY;
if (throttling) return;
throttling = true;
requestAnimationFrame(function () {
throttling = false;
handler(lastX, lastY);
});
},
{ passive: true }
);
} else {
var handler = clickHandlers[effect];
if (!handler) return;
document.addEventListener("click", function (e) {
handler(e.clientX, e.clientY);
}, { passive: true });
document.addEventListener(
"click",
function (e) {
handler(e.clientX, e.clientY);
},
{ passive: true }
);
}
})();
</script>
<style is:global>
.wi-click-fx{position:fixed;pointer-events:none;will-change:transform,opacity
/* 特效层:铺满视口的固定层,统一承载所有点击/拖尾元素 */
.wi-click-fx-layer {
position: fixed;
inset: 0;
z-index: 12050;
pointer-events: none;
overflow: hidden;
}
.wi-click-fx--text{font-size:18px;font-weight:700;transform:translate(-50%,-50%);opacity:0;animation:wi-fx-text 1.2s ease-out forwards
.wi-click-fx {
position: fixed;
pointer-events: none;
will-change: transform, opacity;
user-select: none;
}
.wi-click-fx--heart{font-size:20px;transform:translate(-50%,-50%) scale(1);opacity:1;transition:transform 1s cubic-bezier(.16,1,.3,1),opacity 1s ease-out
.wi-click-fx--text {
font-size: 18px;
font-weight: 700;
transform: translate(-50%, -50%);
animation: wi-fx-text 1.2s ease-out forwards;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
}
.wi-click-fx--spark{width:6px;height:6px;border-radius:50%;transform:translate(-50%,-50%) scale(1);opacity:1;transition:transform .8s cubic-bezier(.16,1,.3,1),opacity .8s ease-out
.wi-click-fx--heart {
font-size: 20px;
opacity: 1;
transition: transform 1s cubic-bezier(0.16, 1, 0.3, 1), opacity 1s ease-out;
}
.wi-click-fx--ripple{width:30px;height:30px;border:2px solid;border-radius:50%;transform:translate(-50%,-50%) scale(.5);opacity:1;transition:transform .7s ease-out,opacity .7s ease-out
.wi-click-fx--spark {
width: 6px;
height: 6px;
border-radius: 50%;
opacity: 1;
transition: transform 0.8s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.8s ease-out;
}
.wi-click-fx--particle{width:8px;height:8px;border-radius:50%;transform:translate(-50%,-50%) scale(1);opacity:1;transition:transform .7s cubic-bezier(.16,1,.3,1),opacity .7s ease-out
.wi-click-fx--ripple {
width: 30px;
height: 30px;
border: 2px solid;
border-radius: 50%;
transform: translate(-50%, -50%) scale(0.5);
opacity: 0.9;
transition: transform 0.7s ease-out, opacity 0.7s ease-out;
}
.wi-click-fx--star{font-size:16px;transform:translate(-50%,-50%) scale(1);opacity:1;transition:transform .8s cubic-bezier(.16,1,.3,1),opacity .8s ease-out
.wi-click-fx--particle {
width: 8px;
height: 8px;
border-radius: 50%;
opacity: 1;
transition: transform 0.7s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.7s ease-out;
}
.wi-click-fx--trail-star{font-size:12px;transform:translate(-50%,-50%) scale(1);opacity:.8;transition:opacity .6s ease-out,transform .6s ease-out
.wi-click-fx--star {
font-size: 16px;
opacity: 1;
transition: transform 0.8s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.8s ease-out;
}
.wi-click-fx--trail-particle{width:6px;height:6px;border-radius:50%;transform:translate(-50%,-50%) scale(1);opacity:.8;transition:opacity .5s ease-out,transform .5s ease-out
.wi-click-fx--trail-star {
font-size: 12px;
transform: translate(-50%, -50%);
opacity: 0.9;
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
}
@keyframes wi-fx-text{0%{opacity:0;transform:translate(-50%,-50%) scale(.5)}20%{opacity:1;transform:translate(-50%,-80%) scale(1.1)}to{opacity:0;transform:translate(-50%,-120%) scale(.9)}}
.wi-click-fx--trail-particle {
width: 6px;
height: 6px;
border-radius: 50%;
transform: translate(-50%, -50%);
opacity: 0.9;
transition: opacity 0.5s ease-out, transform 0.5s ease-out;
}
@keyframes wi-fx-text {
0% {
opacity: 0;
transform: translate(-50%, -50%) scale(0.5);
}
20% {
opacity: 1;
transform: translate(-50%, -80%) scale(1.1);
}
to {
opacity: 0;
transform: translate(-50%, -120%) scale(0.9);
}
}
</style>