上滑跑馬燈

前言

產品說,咱們作一個轉盤活動吧,須要輪播中獎信息。 固然這需求徹底沒有問題。
產品說,你聽我說完。css

  1. 是從下往上輪播
  2. 如何數據沒有更新,就反覆輪播。
  3. 若是數據有更新,要無縫更新。
  4. 進入時間1s,暫停1S,出去時間1s.

沒問題吧。
額, 等等,沒多大問題。 那個誰,這個任務教你啦。css3

方案

而後,個人同事開始蒐羅實現方案,不少都是勻速走的。
同事甚至和產品討論要不要換成跑馬燈,嘻嘻, 開玩笑。
說個笑話,csdn上有很多的這樣代碼,可是下載要積分,我能夠說,日了*狗麼!。git

一個下午天氣晴,有涼風,心情還好。 因而花了一點時間思考了一種方案。github

關於移動端動畫,無非是純js控制,js Animation API(兼容不理想), css動畫,canvas, webgl以及雜交方案。 關於本需求,前兩種應該比較適合,成本低,容易實現。web

純js實現控制比較傳統的方案,要啓用定時器setTimeout/setInterval/requestAnimation等等,我很煩這。canvas

採用css3 + js雜交方案,有戲靠譜。 既然有三個階段,那麼我就把你拆成三段動畫, 隨你去配置,隨你去high。 固然你也能夠用一段動畫,經過設置來控制距離。app

總體的思路ide

  1. 我把每一個須要滾動的每一個元素獨立起來,每一個元素有三段動畫, in , pause , out. 怎麼銜接, 經過animationend事件。
  2. 那麼不一樣元素又怎麼銜接,經過animationDelay來延時啓動動畫, 啓動後依舊是走上面的三段動畫。
  3. 怎麼輪迴播放,固然你能夠利用原來的節點,從新修改屬性,設置延時啓動。 我這裏採用比較簡單的,直接刪除了原有,而後從新建立。 固然從新建立是有講究的,你有不少選擇,只要控制好銜接的事件,我這裏是在最後一個節點開始第一階段運動的時候,從新建立新節點,最後節點第三階段運動結束,清除以前運動完畢的節點
  4. 關於無縫更新,固然要讓最後一個運動的元素運動完, 因此我在第二個階段 pause階段執行新的節點建立,並設置好相關的延時。
  5. 關於暫停能力, animationPlayState提供這個能力,paused和running。 暫停的手,animationDelay也會中止計時,很是的棒。 由於懶,只實現了PC的暫停能力。移動端嘛,添加touch,tap,pointer事件就好。 由於懶,因此。

改進動畫

  1. 每一個元素三個動畫,是有點消耗。能夠用一個大的容器放好全部的元素,而後animationPlayState來控制,蠻不錯的。 如何控制每一個階段的計時呢,固然能夠用js,我有個想法,就是起一個三段動畫,在這個事件裏面去控制animationPlayState。
  2. 節點也沒回收利用,是有點浪費。
  3. 移動端的支持呢?
  4. 不過重要的其餘......

效果呢?

怎麼使用呢?

const contents = [
            "隊列1:春天 - first",
            "隊列1:夏天",
            "隊列1:秋天",
            "隊列1:冬天",
            "隊列1:夏夏湉",
            "隊列1:求七天",
            "隊列1:Who are You - last"
        ];
        const contents2 = [
            "隊列2:這是怎麼回事 - first",
            "隊列2:誰是最可賴的人",
            "隊列2:壯士一去不復返",
            "隊列2:誰來拯救你",
            "隊列2:家福樂團購有沒有 - last"
        ]

        const el = document.querySelector("#box");

        let upSlide = new UpSlide({
            el
        });
        upSlide.start(contents);

        document.getElementById('btnChange').addEventListener("click", () => {
            upSlide.start(contents);
        })

        document.getElementById('btnChange2').addEventListener("click", () => {
            upSlide.start(contents2);
        })

源碼呢?

等等這個有點用, 源碼呢
上滑跑馬燈源碼webgl

再貼出源碼,這樣文章長一點

const DEFAULT_OPTION = {
    inTime: 1000,
    pauseTime: 1500,
    outTime: 1000,
    className: "upslide-item",
    animationClass: "upslide-item-animation",
    animationInClass: "slideup-animation-in",
    animationPauseClass: "slideup-animation-pause",
    animationOutClass: "slideup-animation-out",
    pauseOnFocus: false
};

const DELETING_CLASS_NAME = "__deleting__";

function clearSiblings(el) {
    const parent = el.parentElement;
    // 移除前面節點
    while (el.previousElementSibling) {
        parent.removeChild(el.previousElementSibling);
    }
    // 移除後面的節點
    while (el.nextElementSibling) {
        parent.removeChild(el.nextElementSibling);
    }
}

class UpSlide {
    constructor(options) {
        this.el = options.el;
        this.options = Object.assign({}, DEFAULT_OPTION, options);
        this.changeStatus = 0;
        this.currentContents = null;

        const { inTime, pauseTime, outTime } = this.options;
        this.totalTime = inTime + pauseTime + outTime;
        this.inPausePercent = (inTime + pauseTime) / this.totalTime;
        this.animationstartEvent = this.animationstartEvent.bind(this);
        this.animationendEvent = this.animationendEvent.bind(this);
        this.mouseenterEvent = this.mouseenterEvent.bind(this);
        this.mouseleaveEvent = this.mouseleaveEvent.bind(this);
        this.init();
    }

    createItems(datas, baseDelay = 0) {
        const { className, animationInClass, animationClass, inTime } = this.options;
        const { totalTime, inPausePercent } = this;
        const fragment = document.createDocumentFragment();
        datas.forEach((c, i) => {
            const newEl = document.createElement("div");
            newEl.dataset.isLast = i === datas.length - 1 ? 1 : 0;
            newEl.innerText = c;
            newEl.className = className + " " + animationClass;
            newEl.style.animationName = animationInClass;
            newEl.style.animationDelay = baseDelay + i * totalTime * inPausePercent + "ms";
            newEl.style.animationDuration = inTime + "ms";
            fragment.appendChild(newEl);
        });
        return fragment;
    }

    animationstartEvent(e) {
        const { totalTime, inPausePercent } = this;
        const { animationInClass } = this.options;
        // 開啓新的輪迴
        if (e.animationName === animationInClass && e.target.dataset.isLast == 1) {
            this.innerStart(this.currentContents, totalTime * inPausePercent);
        }
    }

    animationendEvent(e) {
        const {
            animationInClass,
            animationPauseClass,
            animationOutClass,
            className,
            animationClass,
            pauseTime,
            outTime
        } = this.options;

        const { changeStatus } = this;
        const el = e.target;
        const parent = el.parentElement;
        const animationName = e.animationName;

        switch (animationName) {
            case animationInClass:
                el.style.animationName = animationPauseClass;
                el.style.animationDuration = pauseTime + "ms";
                el.style.animationDelay = "0ms";
                break;
            case animationPauseClass:
                el.style.animationName = animationOutClass;
                el.style.animationDuration = outTime + "ms";
                el.style.animationDelay = "0ms";

                // 切換
                if (changeStatus === 1) {
                    clearSiblings(el);
                    // 標記
                    el.classList.add(DELETING_CLASS_NAME);
                    // 切換
                    this.innerStart(this.currentContents, 0);
                    this.changeStatus = 0;
                }
                break;
            case animationOutClass:
                e.target.classList.remove(animationClass);
                e.target.style.animationDelay = "";

                if (el.classList.contains(DELETING_CLASS_NAME)) {
                    parent.removeChild(el);
                }
                // 輪迴結束-清除節點
                if (e.target.dataset.isLast == 1) {
                    const parent = e.target.parentElement;
                    const delItems = parent.querySelectorAll(
                        `.${className}:not(.${animationClass})`
                    );
                    if (delItems.length > 0) {
                        for (let i = delItems.length - 1; i >= 0; i--) {
                            parent.removeChild(delItems[i]);
                        }
                    }
                }
                break;
            default:
                break;
        }
    }

    mouseenterEvent() {
        const { className } = this.options;
        this.el.querySelectorAll("." + className).forEach(el => {
            el.style.animationPlayState = "paused";
        });
    }

    mouseleaveEvent() {
        const { className } = this.options;
        this.el.querySelectorAll("." + className).forEach(el => {
            el.style.animationPlayState = "running";
        });
    }

    init() {
        const { el } = this;
        el.addEventListener("animationstart", this.animationstartEvent);
        el.addEventListener("animationend", this.animationendEvent);
        const { pauseOnFocus } = this.options;
        if (pauseOnFocus === true) {
            el.addEventListener("mouseenter", this.mouseenterEvent);
            el.addEventListener("mouseleave", this.mouseleaveEvent);
        }
    }

    innerStart(content, delay = 0) {
        this.currentContents = content;
        const c = this.createItems(content, delay);
        this.el.appendChild(c);
    }

    start(content, delay = 0) {
        if (this.currentContents != null) {
            this.changeStatus = 1;
            this.currentContents = content;
            return;
        }
        this.innerStart(content, delay);
    }

    destroy() {
        this.el.removeEventListener("animationstart", this.animationstartEvent);
        this.el.removeEventListener("animationend", this.animationendEvent);
        const { pauseOnFocus } = this.options;
        if (pauseOnFocus === true) {
            el.removeEventListener("mouseenter", this.mouseoverEvent);
            el.removeEventListener("mouseleave", this.mouseleaveEvent);
        }
        this.el.innerHTML = null;
        this.el = null;
        this.options = null;
    }
}
相關文章
相關標籤/搜索