/* * 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);