- 替换多张图片资源为PNG格式 - 重构底部播放器样式,调整布局和动画效果 - 优化guess页面的UI元素和交互提示 - 修复CSS中的z-index和line-height问题 - 调整音频预加载策略为auto
380 lines
15 KiB
JavaScript
380 lines
15 KiB
JavaScript
/*
|
||
* 3D标签云(无缩放版)
|
||
* 功能:鼠标移入标签静止、自动3D旋转、GPU加速,移除所有放大缩小效果
|
||
* 说明:radius控制滚动区域,基于translate3d实现3D定位,全程保持标签原始尺寸
|
||
* 版本:3.1
|
||
* */
|
||
window.tagCloud = (function (win, doc) {
|
||
function isObject(obj) {
|
||
return Object.prototype.toString.call(obj) === "[object Object]";
|
||
}
|
||
|
||
function TagCloud(options) {
|
||
var self = this;
|
||
self.config = TagCloud._getConfig(options);
|
||
self.box = self.config.element;
|
||
self.fontsize = self.config.fontsize;
|
||
self.animDuration = self.config.animDuration;
|
||
self.perspective = self.config.perspective;
|
||
|
||
// 初始化滚动区域(半径与比例)
|
||
if (Number.isInteger(self.config.radius)) {
|
||
self._radiusX = self._radiusY = self.config.radius;
|
||
} else if (self.config.radius instanceof Array) {
|
||
self._radiusX = self.config.radius.length >= 1 ? self.config.radius[0] : 60;
|
||
self._radiusY = self.config.radius.length >= 2 ? self.config.radius[1] : self._radiusX;
|
||
}
|
||
self.radius = Math.max(self._radiusX, self._radiusY);
|
||
const ratio = Math.round((self._radiusX * 10) / self._radiusY) / 10;
|
||
self.ratioX = ratio < 1 ? ratio : 1;
|
||
self.ratioY = ratio > 1 ? ratio : 1;
|
||
|
||
// 基础参数
|
||
self.depth = 2 * self.radius;
|
||
self.mspeed = TagCloud._getMsSpeed(self.config.mspeed);
|
||
self.ispeed = TagCloud._getIsSpeed(self.config.ispeed);
|
||
self.items = self._getItems();
|
||
self.direction = self.config.direction;
|
||
self.keep = self.config.keep;
|
||
|
||
// 状态变量
|
||
self.active = false;
|
||
self.mouseX = 0;
|
||
self.mouseY = 0;
|
||
self.index = -1;
|
||
self.keyframeNames = [];
|
||
|
||
// 初始化容器样式(3D透视核心)
|
||
self.box.style.position = "relative";
|
||
self.box.style.visibility = "visible";
|
||
// self.box.style.overflow = "hidden";
|
||
self.box.style.perspective = `${self.perspective}px`;
|
||
self.box.style.transformStyle = "preserve-3d";
|
||
|
||
// 事件绑定
|
||
self._bindEvents();
|
||
|
||
// 生成关键帧与动画
|
||
self._generateAllKeyframes();
|
||
self._applyAnimations();
|
||
|
||
// 实时更新
|
||
self._update = self._update.bind(self);
|
||
requestAnimationFrame(self._update);
|
||
}
|
||
|
||
// 静态属性与方法
|
||
TagCloud.boxs = [];
|
||
|
||
// 数组indexOf兼容
|
||
if (!Array.prototype.indexOf) {
|
||
Array.prototype.indexOf = function (elt) {
|
||
var len = this.length >>> 0;
|
||
var from = Number(arguments[1]) || 0;
|
||
from = from < 0 ? Math.ceil(from) : Math.floor(from);
|
||
if (from < 0) from += len;
|
||
for (; from < len; from++) {
|
||
if (from in this && this[from] === elt) return from;
|
||
}
|
||
return -1;
|
||
};
|
||
}
|
||
|
||
// querySelectorAll兼容
|
||
if (!doc.querySelectorAll) {
|
||
doc.querySelectorAll = function (selectors) {
|
||
var style = doc.createElement("style"),
|
||
elements = [],
|
||
element;
|
||
doc.documentElement.firstChild.appendChild(style);
|
||
doc._qsa = [];
|
||
style.styleSheet.cssText = selectors + "{x-qsa:expression(document._qsa && document._qsa.push(this))}";
|
||
window.scrollBy(0, 0);
|
||
style.parentNode.removeChild(style);
|
||
while (doc._qsa.length) {
|
||
element = doc._qsa.shift();
|
||
element.style.removeAttribute("x-qsa");
|
||
elements.push(element);
|
||
}
|
||
doc._qsa = null;
|
||
return elements;
|
||
};
|
||
}
|
||
|
||
// 配置合并(移除hoverScale配置)
|
||
TagCloud._getConfig = function (config) {
|
||
const defaultConfig = {
|
||
selector: ".tagcloud",
|
||
fontsize: 16,
|
||
radius: [80, 60],
|
||
mspeed: "normal",
|
||
ispeed: "normal",
|
||
direction: 135,
|
||
keep: true,
|
||
multicolour: true,
|
||
animDuration: 15,
|
||
perspective: 800
|
||
};
|
||
if (isObject(config)) {
|
||
for (let i in config) {
|
||
if (config.hasOwnProperty(i) && i !== "hoverScale") { // 禁止传入缩放相关配置
|
||
defaultConfig[i] = config[i];
|
||
}
|
||
}
|
||
}
|
||
return defaultConfig;
|
||
};
|
||
|
||
// 滚动最大速度映射
|
||
TagCloud._getMsSpeed = function (mspeed) {
|
||
const speedMap = { slow: 1.2, normal: 5, fast: 4 };
|
||
return speedMap[mspeed] || 2.5;
|
||
};
|
||
|
||
// 初始速度映射
|
||
TagCloud._getIsSpeed = function (ispeed) {
|
||
const speedMap = { slow: 8, normal: 20, fast: 40 };
|
||
return speedMap[ispeed] || 20;
|
||
};
|
||
|
||
// 角度转正弦余弦集合
|
||
TagCloud._getSc = function (a, b) {
|
||
const l = Math.PI / 180;
|
||
return [Math.sin(a * l), Math.cos(a * l), Math.sin(b * l), Math.cos(b * l)];
|
||
};
|
||
|
||
// 事件绑定兼容
|
||
TagCloud._on = function (ele, eve, handler, cap = false) {
|
||
if (ele.addEventListener) {
|
||
ele.addEventListener(eve, handler, cap);
|
||
} else if (ele.attachEvent) {
|
||
ele.attachEvent("on" + eve, handler);
|
||
} else {
|
||
ele["on" + eve] = handler;
|
||
}
|
||
};
|
||
|
||
// 原型方法
|
||
TagCloud.prototype = {
|
||
constructor: TagCloud,
|
||
|
||
// 获取标签数据(移除缩放相关逻辑)
|
||
_getItems: function () {
|
||
const self = this;
|
||
const items = [];
|
||
const children = self.box.children;
|
||
const len = children.length;
|
||
|
||
for (let i = 0; i < len; i++) {
|
||
const element = children[i];
|
||
// 强制设置标签字体大小(固定,不随深度变化)
|
||
// element.style.fontSize = `${self.fontsize}px`;
|
||
// element.style.lineHeight = "1.5"; // 固定行高,避免尺寸变化
|
||
|
||
const angle = {
|
||
phi: Math.acos(-1 + (2 * i + 1) / len),
|
||
theta: Math.sqrt((len + 1) * Math.PI) * Math.acos(-1 + (2 * i + 1) / len)
|
||
};
|
||
|
||
// 初始3D坐标(球面映射)
|
||
const x = (self.radius / 2) * 1.5 * Math.cos(angle.theta) * Math.sin(angle.phi) * self.ratioX;
|
||
const y = (self.radius / 2) * 1.5 * Math.sin(angle.theta) * Math.sin(angle.phi) / self.ratioY;
|
||
const z = (self.radius / 2) * 1.5 * Math.cos(angle.phi);
|
||
|
||
// 彩色标签配置
|
||
if (self.config.multicolour) {
|
||
const hue = Math.round(Math.random() * 360);
|
||
const lightness = 30 + Math.round(Math.random() * 30);
|
||
element.style.color = `hsl(${hue}, 100%, ${lightness}%)`;
|
||
}
|
||
|
||
// 标签基础样式(移除scale过渡,固定尺寸)
|
||
element.style.position = "absolute";
|
||
element.style.transformStyle = "preserve-3d";
|
||
element.style.transition = "transform 0.3s ease, z-index 0.3s ease, opacity 0.3s ease"; // 仅保留位置、层级、透明度过渡
|
||
element.style.cursor = "pointer";
|
||
element.style.transform = "scale(1)"; // 强制固定缩放为1(无放大缩小)
|
||
element.index = i;
|
||
|
||
items.push({
|
||
element,
|
||
angle,
|
||
x,
|
||
y,
|
||
z,
|
||
offsetWidth: element.offsetWidth,
|
||
offsetHeight: element.offsetHeight
|
||
});
|
||
}
|
||
return items;
|
||
},
|
||
|
||
// 绑定交互事件(无缩放逻辑)
|
||
_bindEvents: function () {
|
||
const self = this;
|
||
|
||
// 容器鼠标移入/移出
|
||
TagCloud._on(self.box, "mouseover", () => { self.active = true; });
|
||
TagCloud._on(self.box, "mouseout", () => { self.active = false; });
|
||
|
||
// 鼠标移动
|
||
const target = self.config.keep ? win : self.box;
|
||
TagCloud._on(target, "mousemove", (ev) => {
|
||
const oEvent = win.event || ev;
|
||
const rect = self.box.getBoundingClientRect();
|
||
self.mouseX = (oEvent.clientX - (rect.left + rect.width / 2)) / 8;
|
||
self.mouseY = (oEvent.clientY - (rect.top + rect.height / 2)) / 8;
|
||
});
|
||
|
||
// 标签鼠标移入/移出(仅静止、置顶,无放大)
|
||
self.items.forEach(item => {
|
||
TagCloud._on(item.element, "mouseover", function () {
|
||
self.index = this.index;
|
||
this.style.zIndex = 999; // 仅置顶,不放大
|
||
});
|
||
TagCloud._on(item.element, "mouseout", function () {
|
||
self.index = -1;
|
||
this.style.zIndex = "";
|
||
});
|
||
});
|
||
},
|
||
|
||
// 生成关键帧(移除缩放逻辑,固定scale=1)
|
||
_generateKeyframe: function (itemIndex) {
|
||
const self = this;
|
||
const keyframeName = `tagCloudAnim_${Date.now()}_${itemIndex}`;
|
||
const styleSheet = doc.createElement("style");
|
||
let keyframeCSS = `@keyframes ${keyframeName} {`;
|
||
|
||
// 拆分360°旋转为20个关键帧
|
||
for (let deg = 0; deg <= 360; deg += 18) {
|
||
const rad = deg * Math.PI / 180;
|
||
const item = self.items[itemIndex];
|
||
const sc = TagCloud._getSc(
|
||
Math.sin(rad) * self.mspeed,
|
||
Math.cos(rad) * self.mspeed
|
||
);
|
||
|
||
// 实时3D坐标计算
|
||
const rx1 = item.x;
|
||
const ry1 = item.y * sc[1] + item.z * -sc[0];
|
||
const rz1 = item.y * sc[0] + item.z * sc[1];
|
||
const rx2 = rx1 * sc[3] + rz1 * sc[2];
|
||
const ry2 = ry1;
|
||
const rz2 = rz1 * sc[3] - rx1 * sc[2];
|
||
|
||
// 固定透明度(或按深度微调,不影响尺寸)
|
||
const per = self.depth / (self.depth + rz2);
|
||
const opacity = Math.max(0.5, per); // 仅调整透明度,无缩放
|
||
|
||
// 容器中心偏移
|
||
const centerX = (self.box.offsetWidth - item.offsetWidth) / 2;
|
||
const centerY = (self.box.offsetHeight - item.offsetHeight) / 2;
|
||
|
||
// 关键帧内容:固定scale=1,仅保留translate3d和opacity
|
||
const percent = (deg / 360) * 100;
|
||
keyframeCSS += `${percent}% {
|
||
transform: translate(${rx2 + centerX}px, ${ry2 + centerY}px) scale(1);
|
||
opacity: ${opacity};
|
||
z-index: ${Math.ceil(per * 10)};
|
||
}`;
|
||
}
|
||
|
||
keyframeCSS += `}`;
|
||
styleSheet.innerHTML = keyframeCSS;
|
||
doc.head.appendChild(styleSheet);
|
||
return keyframeName;
|
||
},
|
||
|
||
// 生成所有标签的关键帧
|
||
_generateAllKeyframes: function () {
|
||
const self = this;
|
||
self.keyframeNames = self.items.map((_, index) => {
|
||
return self._generateKeyframe(index);
|
||
});
|
||
},
|
||
|
||
// 为标签应用动画(无缩放)
|
||
_applyAnimations: function () {
|
||
const self = this;
|
||
self.items.forEach((item, index) => {
|
||
const animName = self.keyframeNames[index];
|
||
// 应用动画:固定scale=1,仅translate3d和opacity变化
|
||
item.element.style.animation = `${animName} ${self.animDuration}s linear infinite`;
|
||
item.element.style.transform = `scale(1)`; // 双重保障,防止缩放
|
||
});
|
||
},
|
||
|
||
// 实时更新(仅静止、调整动画速度,无放大缩小)
|
||
_update: function () {
|
||
const self = this;
|
||
if (self.index === -1 && !self.active) {
|
||
requestAnimationFrame(self._update);
|
||
return;
|
||
}
|
||
|
||
// 选中标签:仅静止、置顶、保持原始尺寸
|
||
if (self.index !== -1) {
|
||
const item = self.items[self.index];
|
||
const centerX = (self.box.offsetWidth - item.offsetWidth) / 2;
|
||
const centerY = (self.box.offsetHeight - item.offsetHeight) / 2;
|
||
// 暂停动画+固定位置+scale=1
|
||
item.element.style.animationPlayState = "paused";
|
||
item.element.style.transform = `translate(${centerX}px, ${centerY}px) scale(1)`;
|
||
item.element.style.opacity = 1;
|
||
} else {
|
||
// 鼠标交互:仅调整动画速度,无缩放
|
||
// self.items.forEach((item, index) => {
|
||
// item.element.style.animationPlayState = "running";
|
||
// const newDuration = self.animDuration - Math.abs(self.mouseX + self.mouseY) / 5;
|
||
// const animName = self.keyframeNames[index];
|
||
// item.element.style.animation = `${animName} ${Math.max(8, newDuration)}s linear infinite`;
|
||
// item.element.style.transform = `scale(1)`; // 确保无缩放
|
||
// });
|
||
}
|
||
|
||
requestAnimationFrame(self._update);
|
||
},
|
||
|
||
// 销毁实例
|
||
destroy: function () {
|
||
const self = this;
|
||
// 移除所有动画样式
|
||
const styles = doc.querySelectorAll('style');
|
||
styles.forEach(style => {
|
||
if (style.innerHTML.includes('tagCloudAnim_')) {
|
||
doc.head.removeChild(style);
|
||
}
|
||
});
|
||
// 移除事件监听
|
||
self.box.onmouseover = null;
|
||
self.box.onmouseout = null;
|
||
self.items.forEach(item => {
|
||
item.element.onmouseover = null;
|
||
item.element.onmouseout = null;
|
||
item.element.style.animation = null;
|
||
item.element.style.transform = "scale(1)";
|
||
});
|
||
// 从实例数组中移除
|
||
const idx = TagCloud.boxs.indexOf(self.box);
|
||
if (idx !== -1) TagCloud.boxs.splice(idx, 1);
|
||
}
|
||
};
|
||
|
||
// 工厂函数
|
||
return function (options = {}) {
|
||
const selector = options.selector || ".tagcloud";
|
||
const elements = doc.querySelectorAll(selector);
|
||
const instances = [];
|
||
|
||
elements.forEach(element => {
|
||
if (TagCloud.boxs.indexOf(element) === -1) {
|
||
TagCloud.boxs.push(element);
|
||
const instance = new TagCloud({ ...options, element });
|
||
instances.push(instance);
|
||
}
|
||
});
|
||
|
||
return instances;
|
||
};
|
||
})(window, document); |