这不快年底了么,公司高层打算把这五年的发展历程做一次回顾巡礼,一方面宣扬一下公司文化,另一方面歌颂一下公司这五年来取得的辉煌成就,单纯的做个海报,写个公众号文章,或整个传统ppt在内部宣讲,再剪个视频啥的已经无法满足公司想众乐乐的强烈情感了。
于是想做一个交互性良好、内容表达形式多样、章节清晰、条理分明、易于传播的“ppt”,于是在公司千人大群里号召,有没有谁能做的,问了好几次,最后只有我应下了这个大活儿。
最后我与一位主要负责这件事的领导(我称为带头大哥)和一位UI小姐姐临时组成了一个小队,来完成这项任务。
(在线观看地址,可以先看看效果。记得手指或者鼠标,向上滑动来播放)
带头大哥没过几日就发我一个文档,上面写了对仗工整,思想明确的“对联”

so,时间这么紧,而且做不完可能会有不小的后果,em~~~~,并且这件事也不是着急干就能干完的,我需要冷静的想一个办法,我忽然想到《代码整洁之道》中好像说过,具体记不清,大体意思是:“越是想快,就要写好代码”。
em~~~,好的,那么我拿出几天设计一个工具吧,不需要多么成熟,只要能够满足容易上手,可以批量制作就行,这样就能够实现人手的增加,效率也会提高的局面,到时完成就变得很有戏了,就这么办。
于是我用了两三天的时间做出了这个工具,并做了一个demo给到了带头大哥,他表示ok,还可以,能接受,而且经过我的指导,他也可以参与进来了,于是我们就开始“愉快地”批量的制作了。
我总希望用最简单的话语,描述一件事,我比较喜欢简单,那么我们就用简单的方式把我的设计讲讲。
视频可以快进,倒退,仅仅通过拨动进度条或者滑动屏幕即可,我喜欢这种交互,那就做成这样,舒服。 首先我不想做成静态资源那种的,比如视频,gif之类的,这种是没有“生命”的,只能称之为“物质”,我简单用“阴”代指,我所需要是基于物质所焕发同时也可以创造物质的的“生命,心灵”,我称他为阳。说ta是“活”的有点过,就是说可以交互,可以整合再利用,随时可以通过ta创建视频,gif之类的静态资源,岂不妙哉。
我希望设计出一系列独立个体,可以很好的串联起整个逻辑,它具备了基本的功能,同时又具备了扩展的能力,可互相联结,又彼此独立,目前有两个主要的个体,一个是单位个体entity,另一个是动作个体recation
entity大体应该具备以下行为:lenPercent和startPercentliveprocessendentityArr
entityrecationArr
recation,recation大体应该具备以下行为:start和endaction()那么个体设计好了,围绕个体所展开的逻辑,就顺理成章了。
代码如下:
- 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))
- }
- });
- },
- });
-
- 复制代码
就这么简单。
这个思路完全可以使用在dom上,完全可以用react和vue实现,我会陆续重构完毕。

领导:阿强,这个就是我们项目的开场。
我:哦哦,懂了,话说,怎么转场呢?
领导:看到小飞机没有~
就这个。

还有第二页的这个。

(A):第一页上来,左下角一个人坐着飞机向上飞,然后小飞机往右上角飞,飞离出去之后,开始转场到二张。
(B)然后第二页左下飞入这个小飞机,飞到中心处,也就是图片上的位置,那么开场part就ok了。)
我:ok,明白了,开整。
A实现:

目标就是让这个节点移动一段距离停下来,那么写一个脚本plane0_1,上来就执行一个移动一段距离的动作。
- import entity from '../../base/entity';
- cc.Class({
- extends: entity,
-
- properties: {
- },
-
- // LIFE-CYCLE CALLBACKS:
-
- onLoad() {
- this._super();
- let self = this;
- // 就是上来,就执行一个运动,moveBy就是一段时间,移动移动距离的动画api
- var action = cc.moveBy(1, cc.v2(this.node.getContentSize().width + 100, 200))
- // 执行动作
- this.node.runAction(action);
- },
- });
-
-
- 复制代码
挂载到节点上就可以了。

这里简单介绍一下,我设置了几个参数:
B实现:主要就是实现这个节点移动飞行

那么根据上面描述的思路,我们不难实现这个小飞机的移动,无非就是设置这个小飞机在这个part里的开始时刻是多少,运动多久,运动到哪,只要把这些定好,动画自动产生。
那么开发脚本
- import entity from '../../base/entity';
- cc.Class({
- extends: entity,
-
- properties: {
- plane: cc.Node
- },
-
- // LIFE-CYCLE CALLBACKS:
-
- onLoad() {
- this._super();
- let self = this;
-
- // 向右上角移动一段距离
- var action = cc.moveBy(1, cc.v2(this.node.getContentSize().width + 500, 500))
- // spawn是同时执行运动的函数,目的是让moveBy运动结合一个缩小的运动,这样,有种越发越远,又越小的视觉感。
- let action2 = cc.spawn(action, cc.scaleTo(1, 2))
- // 执行动作
- this.node.runAction(cc.sequence(cc.delayTime(1), action2, cc.callFunc(() => {
- self.node.active = false;
- self.node.parent.active = false;
- self.plane.runAction(cc.moveTo(1, cc.v2(0, 0)))
- })));
- },
- });
-
- 复制代码
那么下面整体实现,基本都是依靠上面这些思路,实现的,单纯的开始套就能实现下面的动画了。 是不是很神奇,这套思路,完全可以套用到react和vue上。
整好了,领导~,看下效果

领导:行,可以,就这样。
重返这五年,重温那岁月

领导:这张呢~
我:坐飞机的小女孩往上飞出去,做小角的男人飞入画面
领导:差不多,不过坐小飞机的小女孩这块,应该再丰富一点。
我:哦哦哦,懂了,开整
整好了,领导~,看下效果

领导:行,可以,就这样。

我:这个要做成啥样?
领导:入场就是从右边缓缓进来,还有看到这个没

这些圆片一点一点的滑入屏幕,然后屏幕出现内容
这张图
我:懂了,开整
整好了,领导~,看下效果

领导:对,是这样。

整好了,领导~,看下效果

那年启征程,改变从此始

领导:这个该咋设计呢?你看飞机这么多,我们能让这些飞机各飞各的么?
我:可以啊,我试试
整好了,领导~,看下效果

领导:可以的,就这样吧。

还有这个拿笔的人物

整好了,领导~,看下效果


整好了,领导~,看下效果


领导:第一张就是简单的显隐就可以,主要是第二张的这个

我:就像滚动页面那样,哦哦哦,我明白了,开整。
整好了,领导~,看下效果

领导:行,可以的,就这样。

领导:阿强,这个主要是表现我们去的成就,我们仅仅把文字凸显出来就好,其他就不要求了
我:哈哈哈,好的,我试试,效果我尽量做,可能物理效果没那么像,但摞起来的效果肯定是有的。
领导:可以,你试试
我:好的,开整。
整好了,领导~,看下效果

领导:辛苦了,阿强,再加把劲,我们要成功了。
我:好的~~~下一个就是结尾了吧,加油~

many hours later
可算整好了,我的大哥~,效果我是尽力了。

这就是在线预览地址,可以看看效果。
目前项目仅仅用了5天时间就出来了个雏形,还很粗糙,很多地方可以进一步的优化。

这仅仅是一个开始,未来,我会使用react或者vue3,整一个lowcode制作工具,这样就可以更加的方便制作了。
然后开源,敬请期待。
有问题随时交流,我建立一个小清晰qq群,叫“闲D岛”,技术问答群,有问必答,有兴趣可以加群号:551406017
有一说一,我比较低调,一般出风头的事儿,我是没啥想法的,主要是我觉得争名逐利的画面太尴尬,我来不了这个,但也不知我那天是怎么了,当群里问了好几次都没人回应,我就来了点脾气,即然没谁上,那我试试吧。就这样我接下了这个任务,现在再想,可能就是因为这点脾气,我才敢去做的,也许有点莽了,如果没做成呢?哈哈哈,那就不能想了,还好我实现了,我让这个脾气变得更像勇气了,有时候真不妨大胆一点,觉得自己可以,那就试一试,真没准行呢,别怕输,输丢什么人,怕才丢人呢。