我作了一個製做代碼演示動畫的工具,答應我之後別再熬夜作動畫了

這是我參與更文挑戰的第10天,活動詳情查看: 更文挑戰前端

項目源碼node

作這個工具的動機

偶然間我往webpack標籤中發了一篇文章,碰巧看到了然叔寫的作了一晚上動畫,讓你們十分鐘搞懂Webpack,我並不瞭解然叔,但我以爲他極可能比我大,考慮到一位如此努力分享技術的大哥,爲了奉獻本身的光和熱,作了一晚上,那屬實讓人有些心疼,故我想盡個人一點力,爲大佬和你們們也發出個人一份熱。react

我被吸引了

首先領略了一下文章,並印證了「不用想確定比我參悟深,大佬就是大佬」這個」偏見「,但最直接吸引我並非webpack😂,而是一束「光」,對就是大佬寫了一晚上的動畫,我感嘆動畫製做的如此之精巧,表達效果居然如此之顯著,直接地,強烈地吸引了我。webpack

行動起來

與其沉浸在喜好和膜拜的感受中,不如藉着這份情感作點事。我心中審視了一下本身現有的能力,很快就得出了一個想法💡,我好像能整出來一個作出這種「光」的輪子,ok,說幹就幹。git

描繪出本身的指望就成功了一半

首先我不想作成靜態資源那種的,好比視頻,gif之類的,這種是沒有「生命」的,只能稱之爲「物質」,我簡單用「陰」代指,我所須要是基於物質所煥發同時也能夠創造物質的的「生命,心靈」,我稱他爲陽。說ta是「活」的有點過,就是說能夠交互,能夠整合再利用,隨時能夠經過ta建立視頻,gif之類的靜態資源,豈不妙哉。github

基於指望我肯定了實現方案

首先做爲一個前端,技術面要打開,就很容易接觸到形形色色的技術,那麼渲染引擎就是之一。首先出於個人需求,經過渲染引擎開發這會大大的提升開發的效率,其次也能夠整合進我寫的腳手架「Moderate」中,是一個優解。web

設計開發

我總但願用最簡單的話語,描述一件事,我比較喜歡簡單,那麼咱們就用簡單的方式把個人設計講講。markdown

經過鼠標或觸摸滑動,推動動畫進程

視頻能夠快進,倒退,僅僅經過撥動進度條或者滑動屏幕便可,我喜歡這種交互,那就作成這樣,舒服。app

設計好個體,很是關鍵

我但願設計出一系列獨立個體,能夠很好的串聯起整個邏輯,它具有了基本的功能,同時又具有了擴展的能力,可互相聯結,又彼此獨立,目前有兩個主要的個體,一個是單位個體entity,另外一個是動做個體recationide

entity大致應該具有如下行爲:

  • 描述自身運行的週期: lenPercentstartPercent
  • 生命週期函數
    • 開始:live
    • 渲染:process
    • 結束:end
  • 能夠裝載其餘個體的能力:entityArr
    • 組成單位爲:entity
  • 執行動做的集合:recationArr
    • 組成單位爲:recation,

recation大致應該具有如下行爲:

  • 描述自身運行的週期:startend
  • 執行的動做: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和掘友,收穫良多。最後由衷地感謝掘金營造的社區氛圍,感嘆相見恨晚,但一見如故,自強不息,將來可期。

相關文章
相關標籤/搜索