视图跳转动画

概述

视图跳转动画,是指活动视图从一个视图切换至另外一个视图的过程中,展现给用户的动画效果。

View.js 提供的默认样式中,是不含跳转动画的。

设置动画

设置视图跳转动画时,开发者需要了解到视图的四种进入方式:

  1. 压入堆栈式进入

  2. 替换栈顶式进入

  3. 浏览器后退进入

  4. 浏览器前进进入

如果视图的跳转动画是不区分进入方式的,如:渐隐渐显,那么开发者不需要考虑上述差异,按照固定的模式实现即可。但如果想要根据进入方式的不同,呈现差异化的跳转动画,开发者就需要捕获进入方式,然后做出不同的响应。

例如:

技术上,View.js 建议开发者使用 CSS3 animation,结合少量脚本实现跳转动画。具体流程如下:

  1. 使用 CSS,借助 animation、transition 等特性创建视图离开动画、视图进入动画

  2. 有条件地(例如:DOM 元素的 class 含有特定取值时)为视图的 DOM 元素引用动画并确定动画播放时长

  3. 通过 View.setSwitchAnimation 方法,以设置动画的“播放触发器”

对于动画的展现效果,以及动画的播放时长,View.js 均不加以限制,开发者可以自由定义。

由于 View.js 并不知道开发者定义的动画的播放时长,因此开发者需要在设置的动画播放触发器中使用定时器 延迟相同时间 后呈现界面。

下面,我们将从代码细节分析、说明动画的实现过程。

代码分析

HTML + CSS

<!-- 视图1的DOM骨架 -->
<section data-view-id = "page1" data-view-default = "true" >
    <header>
        <span class = "nav-back" data-view-rel = ":back"></span>
        Page 1
    </header>
    <h1>This is page 1.</h1>
    <div data-view-rel = "page2" class = "btn">Navigate to page 2.</div>
</section>

<!-- 视图2的DOM骨架 -->
<section data-viw-id = "page2">
    <header>
        <span class = "nav-back" data-view-rel = ":back"></span>
        Page 2
    </header>
    <h1>This is page 2.</h1>
    <div data-view-rel = "page3" class = "btn">Navigate to page 3.</div>
</section>

<!-- 视图3的DOM骨架 -->
<section data-view-id = "page3">
    <header>
        <span class = "nav-back" data-view-rel = ":back"></span>
        Page 3
    </header>
    <h1>This is page 3.</h1>
    <div data-view-rel = ":default-view" class = "btn">Navigate to page 1.</div>
</section>

JS

;(function () {
    var timer;

    /**
     * 动画持续时长,需要与css中定义的动画时长一致
     * @type {number}
     */
    var animationDuration = 600;

    /**
     * 判断给定的对象是否包含指定名称的样式类
     */
    var hasClass = function (obj, clazz) {
        if (null == clazz || (clazz = String(clazz).trim()) === "")
            return false;

        if (obj.classList && obj.classList.contains)
            return obj.classList.contains(clazz);

        return new RegExp("\\b" + clazz + "\\b", "gim").test(obj.className);
    };

    /**
     * 为指定的对象添加样式类
     */
    var addClass = function (obj, clazz) {
        if (null == clazz || (clazz = String(clazz).trim()) === "" || hasClass(obj, clazz))
            return;

        if (obj.classList && obj.classList.add) {
            obj.classList.add(clazz);
            return;
        }

        obj.className = (obj.className.trim() + " " + clazz).trim();
    };

    /**
     * 为指定的对象删除样式类
     */
    var removeClass = function (obj, clazz) {
        if (null == clazz || (clazz = String(clazz).trim()) === "" || !hasClass(obj, clazz))
            return;

        if (obj.classList && obj.classList.remove) {
            obj.classList.remove(clazz);
            return;
        }

        clazz = String(clazz).toLowerCase();
        var arr = obj.className.split(/\s+/),
            str = "";
        for (var i = 0; i < arr.length; i++) {
            var tmp = arr[i];
            if (null == tmp || (tmp = tmp.trim()) === "")
                continue;

            if (tmp.toLowerCase() === clazz)
                continue;

            str += " " + tmp;
        }
        if (str.length > 0)
            str = str.substring(1);
        obj.className = str.trim();
    };


    /**
     * 清除给定DOM元素上声明的动画样式
     * @param {HTMLElement} obj
     */
    var clear = function (obj) {
        if (!obj)
            return;

        "hideToLeft, showFromRight, hideToRight, showFromLeft".split(/\s*,\s*/).forEach(function (className) {
            removeClass(obj, className);
        });
    };

    /**
     * @param {Object} meta 切换信息
     * @param {HTMLElement} meta.srcElement 视图切换时,要离开的当前视图对应的DOM元素。可能为null
     * @param {HTMLElement} meta.targetElement 视图切换时,要进入的目标视图对应的DOM元素
     * @param {String} type 视图切换方式
     * @param {String} trigger 视图切换触发器
     * @param {Function} render 渲染句柄
     */
    View.setSwitchAnimation(function (meta) {
        var srcElement = meta.srcElement,
            tarElement = meta.targetElement,
            type = meta.type,
            trigger = meta.trigger,
            render = meta.render;

        /**
         * 动画播放前清除可能存在的动画样式
         */
        clear(srcElement);
        clear(tarElement);

        /**
         * 调用View.js传递而来的渲染句柄,完成活动视图的切换,包括:
         * 1. 视图参数的传递
         * 2. 活动视图样式类的切换
         * 3. leave,ready、enter等事件的触发
         */
        render();

        var isNav = type === View.SWITCHTYPE_VIEWNAV,
            isChange = type === View.SWITCHTYPE_VIEWCHANGE,
            isHistoryBack = type === View.SWITCHTYPE_HISTORYBACK,
            isHistoryForward = type === View.SWITCHTYPE_HISTORYFORWARD;

        if (/\bsafari\b/i.test(navigator.userAgent) && (isHistoryBack || isHistoryForward) && trigger ===
            View.SWITCHTRIGGER_NAVIGATOR)
            return;

        /**
         * 视图切换动作是“替换堆栈”的方式,或浏览器不支持对history的操作
         */
        if (!View.checkIfBrowserHistorySupportsPushPopAction() || isChange) {
            addClass(srcElement, "fadeOut");
            addClass(tarElement, "fadeIn");
        } else if (isHistoryForward || isNav) {
            /**
             * 视图切换动作是“压入堆栈”的方式(浏览器前进,或代码触发)
             */

            addClass(srcElement, "hideToLeft");
            addClass(tarElement, "showFromRight");
        } else {
            /**
             * 视图切换动作是“弹出堆栈”的方式(浏览器后退)
             */

            addClass(srcElement, "hideToRight");
            addClass(tarElement, "showFromLeft");
        }

        /**
         * 动画播放完成后清除动画样式
         */
        clearTimeout(timer);
        timer = setTimeout(function () {
            clear(srcElement);
            clear(tarElement);
        }, animationDuration);
    });
})();

其中 View.setSwitchAnimation() 用于向 View.js 提供 “播放触发器”,告知 View.js 在何时播放什么动画:

  1. 压入堆栈时,源视图向左滑动隐藏,目标视图从右向左显示

  2. 弹出堆栈时,源视图向右滑动隐藏,目标视图从左向右显示

Last updated