问题分类
loading 动画的可编辑
问题描述
电子课本之前的书本加载 loading 不满足章节之间的跳转 loading动画的要求,需要制作新的 loading 动画
原因分析
无
解决方案
制作一个 loading 动画并能编辑其最终效果:
下面是最终实现调用的代码:
const _obj = {
interval: "0.15s",
bgColor: "#1bf",
size: "20px",
shrink: 3.75,
};
const config = mkResponsive(_obj);
// 至此实现了数据到视图层的绑定
// setTimeout(() => {
// config.bgColor = "#f00";
// config.size = "50px";
// config.shrink = 5;
// config.interval = "0.2s";
// }, 2000);
// 至此实现编辑器视图到数据的绑定
editor.append(config,"interval","","速度:","text");
editor.append(config,"shrink","","伸缩度:","range",1,10);
editor.append(config,"size","px","点直径:","range",10,100);
editor.append(config,"bgColor","","点颜色:","color");
大体分两步,第一步实现数据到动画的绑定,这里特意做了多个属性同步更改只实现一次重刷的效果,类似 React 使用 SetState 收集帧前状态,但是因为这里使用类似 Vue3 的 Proxy 对象,因此可以直接通过点操作符实现,当然具体实现要简单很多。
第二步是编辑器到数据的绑定,也就是 css3 自带 input 类元素的变动会反射到数据上。
这样,最终效果就是:编辑器 => 数据 => 动画。
首先实现 css 动画,一个 div 包含四个小球,此 div 旋转 45 度后得到另一个 div:
每个小圆给到一个不断循环 scale 的 animation 播放,8 个小球依次具备 interval 时长的播放时差,因为单个 html 文件,直接用 css 变量:
.dots2 > li:nth-child(1) {
animation-delay: calc(var(--interval) * -7);
}
.dots1 > li:nth-child(2) {
animation-delay: calc(var(--interval) * -6);
}
.dots2 > li:nth-child(2) {
animation-delay: calc(var(--interval) * -5);
}
.dots1 > li:nth-child(3) {
animation-delay: calc(var(--interval) * -4);
}
.dots2 > li:nth-child(3) {
animation-delay: calc(var(--interval) * -3);
}
.dots1 > li:nth-child(4) {
animation-delay: calc(var(--interval) * -2);
}
.dots2 > li:nth-child(4) {
animation-delay: calc(var(--interval) * -1);
}
然后是 css 中定义的动画相关的变量,用来方便 js 统一修改:
:root {
--interval: 0.2s;
--shrink: 3.75;
--bgColor: #1bf;
--size: 20px;
}
接下是数据响应式的实现:
// 响应式处理器生成工厂
const responsiveHandlerFactory = () => {
const setPropHandlers = [];
const setPropHandlerCtxMap = new Map();
const addSetPropHandler = (f) => {
setPropHandlers.push(f);
setPropHandlerCtxMap.set(f, Object.create(null));
};
const delSetPropHandler = (f) => {
const index = setPropHandlers.indexOf(f);
if (index !== -1) {
setPropHandlers.splice(index, 1);
setPropHandlerCtxMap.delete(f);
}
};
const responsiveHandler = {
set(obj, prop, val) {
for (let f of setPropHandlers) {
f(setPropHandlerCtxMap.get(f), obj, prop, val);
}
obj[prop] = val;
return true;
},
};
return {
responsiveHandler,
addSetPropHandler,
delSetPropHandler,
};
};
这里是一个极其简单粗糙的实现,给每个注册进来的 handler 创建一个独立的上下文,且使用 Map 记录。
这样做的原因是为了实现每个代理对象属性修改后的延迟响应。
这样使用 addSetPropHandler 就可以注册对象属性变更后的 UI 渲染逻辑了。
最后再实现一个极简编辑器:
// 编辑器
const editor = {
edbox: null,
init() {
this.edbox = document.createElement("div");
const style = {
position: "fixed",
left: "20px",
top: "20px",
width: "300px",
height: "200px",
border: "1px solid #ccc",
borderRadius: "5px",
padding: "10px"
};
((obj) => {
for (let key in obj) {
this.edbox.style[key] = obj[key];
}
})(style);
document.body.appendChild(this.edbox);
},
handlers: {},
append(obj, prop, unit, showText, type, min, max) {
let str = "";
// 生成随机函数名
const fname = "f" + Math.random().toString().slice(-5);
this.handlers[fname] = (val) => {
obj[prop] = val + unit;
};
str = `<div><span>${showText}</span><input type="${type}" ${
type === "range" && ` min=${min || 0} max=${max || 1}`
} value="${obj[prop]}" on${type === "range" ? "change" : "blur"}="editor.handlers.${fname}(event.target.value)" /></div>`;
this.edbox.innerHTML += str;
},
};
纠正措施
案例推广
本案例可推广到XXX项目(XXX项目也存在本案例的问题)