refactor(页面布局): 重构页面布局和样式
- 移除未使用的HTML文件和冗余代码 - 调整侧边栏位置和样式 - 优化标签云组件交互和性能 - 更新播放器控件样式和功能 - 改进预览弹窗的背景透明度 - 添加favicon图标
This commit is contained in:
380
static/js/tagcloud-3.1.js
Normal file
380
static/js/tagcloud-3.1.js
Normal file
@@ -0,0 +1,380 @@
|
||||
/*
|
||||
* 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);
|
||||
Reference in New Issue
Block a user