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:
1 parent
a127d5a314
commit
125d0592c9
42 files changed
+951
-123
No files matched your search
+203
-117
@@ -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 = "❤";
|
||||
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 = "★";
|
||||
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 = "★";
|
||||
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 = "★";
|
||||
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>
|
||||
Reference in new issue
Block a user