问题分类
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项目也存在本案例的问题)