这是我参与更文挑战的第10天,活动详情查看: 更文挑战
做这个工具的动机
偶然间我往webpack
标签中发了一篇文章,碰巧看到了然叔写的做了一夜动画,让大家十分钟搞懂Webpack,我并不了解然叔,但我觉得他很可能比我大,考虑到一位如此努力分享技术的大哥,为了奉献自己的光和热,做了一夜,那属实让人有些心疼,故我想尽我的一点力,为大佬和大家们也发出我的一份热。
我被吸引了
首先领略了一下文章,并印证了“不用想肯定比我参悟深,大佬就是大佬”这个”偏见“,但最直接吸引我并不是webpack
😂,而是一束“光”,对就是大佬写了一夜的动画,我感叹动画制作的如此之精巧,表达效果竟然如此之显著,直接地,强烈地吸引了我。
行动起来
与其沉浸在喜爱和膜拜的感觉中,不如借着这份情感做点事。我心中审视了一下自己现有的能力,很快就得出了一个想法💡,我好像能整出来一个做出这种“光”的轮子,ok,说干就干。
描绘出自己的期望就成功了一半
首先我不想做成静态资源那种的,比如视频,gif之类的,这种是没有“生命”的,只能称之为“物质”,我简单用“阴”代指,我所需要是基于物质所焕发同时也可以创造物质的的“生命,心灵”,我称他为阳。说ta是“活”的有点过,就是说可以交互,可以整合再利用,随时可以通过ta创建视频,gif之类的静态资源,岂不妙哉。
基于期望我确定了实现方案
首先作为一个前端,技术面要打开,就很容易接触到形形色色的技术,那么渲染引擎就是之一。首先出于我的需求,通过渲染引擎开发这会大大的提高开发的效率,其次也可以整合进我写的脚手架“Moderate”中,是一个优解。
设计开发
我总希望用最简单的话语,描述一件事,我比较喜欢简单,那么我们就用简单的方式把我的设计讲讲。
通过鼠标或触摸滑动,推进动画进程
视频可以快进,倒退,仅仅通过拨动进度条或者滑动屏幕即可,我喜欢这种交互,那就做成这样,舒服。
设计好个体,非常关键
我希望设计出一系列独立个体,可以很好的串联起整个逻辑,它具备了基本的功能,同时又具备了扩展的能力,可互相联结,又彼此独立,目前有两个主要的个体,一个是单位个体entity
,另一个是动作个体recation
entity
大体应该具备以下行为:
- 描述自身运行的周期:
lenPercent
和startPercent
- 生命周期函数
- 开始:
live
- 渲染:
process
- 结束:
end
- 开始:
- 可以装载其他个体的能力:
entityArr
- 组成单位为:
entity
- 组成单位为:
- 执行动作的集合:
recationArr
- 组成单位为:
recation
,
- 组成单位为:
recation
大体应该具备以下行为:
- 描述自身运行的周期:
start
和end
- 执行的动作:
action()
那么个体设计好了,围绕个体所展开的逻辑,就顺理成章了。
代码如下:
export default cc.Class({
extends: cc.Component,
properties: {
lenPercent: cc.Float,
startPercent: cc.Float,
isAutoStart: cc.Boolean,
entityArr: {
default: [],
type: cc.Node
}
},
//externalDuration:外部时间(父节点传过来的时间),由父节点决定
//internalDuration:自己内部定的时间,有自己决定,
//为什么要区分两个呢?由于外部应该只能确定我的播放时间,不应该决定我的播放速率,而后者应该有个体自身决定,
//startTime和endTime:由父节点指定的开始和结束时间,(根据父节点的世界定的‘外部时间’!!!)
//timeLine-表示时间到哪了,(根据父节点的世界定的‘外部时间’!!!)
//totaTime-表示我在父节点应该播放的总时长,(根据父节点的世界定的‘外部时间’!!!)
//progressValue就是通过父节点传过来的timeLine,totaTime,timeLine得出我处于的播放进度百分比
//相应的往自己的子节点传的就得参照自己的
ctor() {
this.isLive = false;
this.startTime = undefined;
this.endTime = undefined;
this.internalDuration = 0;//个体内部的时长
this.externalDuration = 0;//个体相对父级的时长
this.progressValue = 0;
this.entryData = [];
this.recationArr = [];
this.startPosition = cc.v2();
this.entityArrEx = [];
},
// LIFE-CYCLE CALLBACKS:
start() {
this.startPosition = this.node.position;
},
onLoad() {
this.node.comName = this.__classname__;
this.internalDuration = this.node.getContentSize().height;
//防止设置的时间太长,强制设置为剩余的时长
if (this.lenPercent + this.startPercent > 1) {
this.lenPercent = 1 - this.startPercent;
}
if (this.isAutoStart) {
this.startPercent += Math.abs((this.node.position.y / this.node.parent.getContentSize().height));
}
},
onEnable() {
let self = this;
if (this.entityArr.length) {
this.entityArrEx = this.entityArr.map((item, index) => {
let entity = item.getComponent(item._name);
if (entity.isAutoStart) {
}
this.entryData.push(entity.initData({
startTime: this.getStarTime(entity.startPercent),
totaTime: self.internalDuration,
}));
return entity;
});
}
},
//业务接口
getStarTime(value) {
if (value <= 1) {
return value * this.internalDuration
} else {
return value
}
},
initData({ totaTime, startTime }) {
this.startTime = startTime;
this.externalDuration = this.lenPercent <= 1 ? totaTime * this.lenPercent : this.lenPercent;
//结束时间最大只能是父类节点结束时间
//因为父节点结束,子节点也必须结束
this.endTime = Math.min(totaTime, this.startTime + this.externalDuration);
return {
startTime: this.startTime,
internalDuration: this.internalDuration,
endTime: this.endTime
}
},
getCurrentTime(percent) {
return (
this.startTime + (percent <= 1 ? this.externalDuration * percent : percent)
);
},
live() {
this.isLive = true;
},
calcProgress() {
this.progressValue = (this.timeLine - this.startTime) / this.externalDuration;
},
calcReactionProgress({ start, end }) {
start = (start <= 1) ? this.internalDuration * start : start;
end = (end <= 1) ? this.internalDuration * end : end;
return Math.min((this.progressValue * this.internalDuration - start) / (end - start), 1);
},
process({ timeLine }) {
this.timeLine = timeLine;
this.calcProgress();
this.internalTimeLine = this.progressValue * this.internalDuration;
let actionArr = this.recationArr.filter((item) => {
if (item) {
let isOk = (timeLine > this.getCurrentTime(item.start) &&
timeLine <= this.getCurrentTime(item.end)) ||
(!item.start && !item.end)
if (isOk) {
item.isAction = true
} else {
if (item.isAction) {
item.action(this.calcActionData(item, true))
}
item.isAction = false
}
return isOk;
}
});
actionArr.forEach((item) => {
item.action(this.calcActionData(item));
});
},
update() {
let self = this;
this.actionEntityArr = this.entityArrEx.filter((entity) => {
if ((self.internalTimeLine) > entity.startTime && self.internalTimeLine <= entity.endTime) {
if (!entity.isLive) {
entity.live();
}
entity.process({
timeLine: self.progressValue * self.internalDuration,
});
return true;
} else {
if (entity.isLive) {
entity.end();
}
}
return false;
});
},
calcActionData(item, isEnd) {
let params = {};
let actionLen = (item.end - item.start) || 1;
let progress;
progress = Math.min((this.progressValue - item.start) / actionLen, 1);
if (isEnd) {
let isEndForce = window.GLOBAL.dir > 0;
let isEndForceStart = window.GLOBAL.dir < 0;
if (isEndForce) {
progress = 1
} else if (isEndForceStart) {
progress = 0
}
params = {
isEndForce: isEndForce,
isEndForceStart: isEndForceStart
}
}
params = {
actionLen,
progress,
...params,
...item
}
return params;
},
end() {
this.isLive = false;
//如果滑动非常快,并且是快进而非后退,那么就要直接强行设置反馈为结束
// if (window.GLOBAL.dir > 0) {
// }
this.recationArr.forEach(item => {
if (item.isAction) {
item.isAction = false
item.action(this.calcActionData(item, true))
}
});
},
});
看下效果:
这是然哥的做的动画
我的版本(做的时候才发现这俩是一体的)
我的意外收获
就在我开始着手做然叔第三个动画,摸索箭头如何实现的时候,机缘巧合地我有了另一个新的灵感,我做出了这个。
“Moderate”新首页。
感谢然叔。
结语
这是我为“Moderate”写专栏的第十篇了,很有成就感,虽然不受欢迎,但整个过程我是快乐的。我遇见了好多好多热情的,才华的,可敬的coder和掘友,收获良多。最后由衷地感谢掘金营造的社区氛围,感叹相见恨晚,但一见如故,自强不息,未来可期。