“更多|收起”交互中渐进使用transition动画

2012-10-19 23:54

“更多|收起”交互中渐进使用transition动画

by 张 鑫旭

at 2012-10-19 15:54:10

original http://www.zhangxinxu.com/wordpress/2012/10/more-display-show-hide-tranisition/

by zhangxinxu from http://www.zhangxinxu.com
本文地址:http://www.zhangxinxu.com/wordpress/?p=2717

一、这是个平淡的开头

在web页面上,类似于“展开更多”、“显示全部”这类显隐效果,就跟大上海的私家车一样,随处可见。随便Open两个页面,就可见到影分身:
豆瓣网上的 大众点评的

一般而言,且平心而论,这类交互效果应当就是这样子的——“唐突的显隐交互”,即内容“啪”一下子呈现或隐藏。

简洁明快,快速消费,且性能不错,实现以及后期维护成本也很低;对于功能性,多交互的商业站点而言,诸多权衡,确实是不错的选择。不过,类似广告宣传的展示性网站,更注重炫酷效果,唐突以及生硬的效果显然是接受不了的。

还有可能接受不了的是年轻气盛的交互设计师——其可能无法忍受一段内容唐突地出现在用户面前——而不是slideDown这类柔和的动画形式——因为前者可能会让用户觉得不舒服,体验不够好。

我突然想到了网上常常会出现的一句话:强奸着强奸着,也就习惯了~~

想到这句话,说明自己也矛盾,或者说自己还没有想明白:整站流畅交互固然不错,就像Apple的产品一样,会有非常不错的体验。但是,实际上,自己内心真正想法是:如果网站交互都带有动画性质,反而很不好;即使将实现以及性能等因素撇开不谈,也是不合适的;因为,网站可能需要自己的气质,过多的效果可能会显得浮躁~~

这种感觉就像是:一个中国妹子,全身上下都是扎眼的高档装饰品,不见得好看(Apple这样的名媛或贵妇另当别论);反而只有头上插了朵小花,更好看。所谓画龙点睛,1~2处惊艳即可,如果龙身上画满了眼睛——我勒个去,密集恐惧~~

听说有个“二八法则”的,如果应用在显隐toggle交互上,8分“啪”显隐,2分“呼”显隐??——我不确定,你怎么看呢?

二、“更多|收起”喜欢的动画

虽然,web世界中,交互动画效果N多多,但是,很多都是约定俗成的,或者称之为“有固定套路的”。

根据David Kaneda创建的Transitions动画CSS代码,我们可以将效果归结为这几大类:slide(滑来滑去), fade(淡入淡出), flip(飞来飞去), pop(大大小小).

如居中弹框呈现与隐藏,适合pop效果;绝对定位浮动层(如智能提示下拉框,自定义时间选择控件)等的呈现与隐藏使用fade效果;幻灯片播放的广告位效果一般为slide效果;点击某商品飞入页面右下角或左上角的购物车就是flip效果(类似最新FireFox浏览器关闭标签页效果)。

而对于页面上,“展开更多|收起更多”这类交互,由于元素默认隐藏,且显示内容就在点击区域附近,因此,想要避免唐突,适合的效果只能是从无到有慢慢“滑出来”-也就是slide效果。

正统的slide效果与伪slide效果
熟知jQuery的人应该都知道,其效果API中有slideDown(), slideUp(), slideToggle()的动画效果API. 那这几个方法是正统的slide效果呢还是伪slide效果?

答案是:伪slide效果!

正统的slide效果,应该是元素整体位置的移动,如这个demo所示的效果,您可以狠狠地点击这里:正统slide效果演示demo
正统slide元素下面先显示 张鑫旭-鑫空间-鑫生活

而jQuery slideDown()/slideUp()效果中,元素本身并没有移动,类似这个demo所示效果:伪slide效果演示demo
伪slide效果元素的上部分先显示 张鑫旭-鑫空间-鑫生活

在我们实际制作页面的时候,采用的都是“伪slide效果”,为何?

因为更低的成本!归根结底是因为:一般的元素(都是非定位元素)是从上往下一次排下来的。因此,通过控制height来实现slide效果的时候,都是元素的上半部分先显示。正统的slide效果的实现需要将动画呈现元素改成定位元素(应用position:absolute/fixed/…),这显然只适用于某些特殊的情况……

因此,作为API, 插件或公共方法,所实现的slide效果都是“伪slide效果”。

三、jQuery中滑动API

实现的原理
一图胜前言,下图为slideDown效果进行中的时候,对动画元素HTML代码的截图:
jQuery slide效果进行中的截图 张鑫旭-鑫空间-鑫生活

因此,可以知道,jQuery的slide效果是同时变更元素的height/margin-top/margin-bottom/padding-top/padding-bottom/ + 设置overflow:hidden值实现的。

注意:没有同时变更border-top/border-bottom的宽度值,因此,如果元素的边框较大,slide效果在结束的瞬间会有顿一下的感觉。在jQuery1.6版本中,宽边框元素无论是slideDown效果还是slideUp效果,在动画要结束的时候,都会有明显的顿感;在jQuery1.8版本中,slideDown效果从头到尾都算流程;但是slideUp效果结束时候有顿感。

您可以狠狠地点击这里:jQuery1.6以及jQuery1.8版本slide效果对比demo

IE7浏览器下效果的顿感以及bug截图 张鑫旭-鑫空间-鑫生活

应用局限
个人观点,jQuery中的slide滑动效果,看上去还那么回事,实际上,是个鸡肋API.

1. 依赖jQuery库。jQuery库本身越来越庞大,比方说jQuery1.8 gzip后居然还有30多K,个人觉得相当大,也相当笨重了。实际上,对于实际项目,选择器没有必要那么强大,有一半的API可以阉割掉,gzip后8~9K足矣,下图我最近某项目使用jQuery文件:
jQuery核心代码压缩后gzip后大小

2. 效果本身不是很完美。如上面演示的,边框元素顿感,以及IE6/IE7浏览器下直接显示bug.

3. 性能问题。如果页面复杂,本身交互比较多。对于IE6这类浏览器,如此动画效果,必定会有卡、顿的感觉;甚至会浏览器扛不住直接崩掉。因此,在实际项目的时候,slide动画使用并不多。尤其对于“更多|收起”交互,因为切换元素都不是绝对定位元素,因此,强烈的重绘会让CPU激动不已!

4. 过分兼容。我觉得,就算应用slide效果,最好IE6~7下直接“唐突显示”,其他现代浏览器浏览器则动画什么的。什么样车有什么样的的马力,什么样的马力跑什么样的速度!……

因此,如果我们非得应用一些slide交互效果,我们可以需求其他更简单,更方便,更灵活的方法。例如,借助CSS3中的transition, 渐进实现一些动画效果。

五、transition实现 – 真的很简单

拿上面出现的“正统slide效果页面”同样效果举例,您可以狠狠地点击这里:transition实现正统slide效果demo

上demo IE10+, 最近版本的FireFox, Chrome, Opera浏览器都可以看到非常流畅的slide动画效果。

相比之前模拟动画demo,这里多了这么行CSS代码:

.container {
    transition: height 0.6s;
}

然后,JS代码就基本上全部消灭了,只留下改变高度的几行代码:

var display = false;

button.onclick = function() {
    display = !display;
    container.style.height = display? "192px": "0px"
    return false;
};
})();

简单,真好!我突然诗兴大发:“脱裤子般简单,拉大便般舒畅;啊!真好,真好!”
众人:“好诗!好诗!!”

六、transition实现的公共方法

在展示transition slide公共方法前,有必要讲下CSS3 transition动画效果出现的条件:

  1. 动画属性前后不同值必须含数值,例如height:0height: auto是没有动画效果的
  2. 不能属性值的应用前后必须有时间差,例如:
    element.style.height = "0px";
    element.style.height = "100px";

    虽然这里元素高度设置了0, 也设置了100像素。但是,由于浏览器的性能机制,只会直接渲染后面100像素高度,因此,是不会出现动画效果的!

因此,考虑到在实际的交互中,显示元素的高度是不可能都已知的,我们要实现transition的公共方法的最难点,就是得到展开元素的精确高度值(height:auto是不会有动画效果的)。

本着简单,人人可以上手的指导思想,我们需要一些约定,关于CSS以及HTML结构的。

HTML结构:

container
    box
        list
        list

其中container以下CSS是必不可少的(如果内部子元素没有绝对定位元素,position:relative可以省略),就是下面这样(私有前缀这里省掉了):

.container {
    height: 0; position: relative;  overflow: hidden; transition: height 0.6s;
}

其中,动画时间你自己控制,你还可以增加缓动类型,例如ease:

.container {
    transition: height 0.3s ease;
}

对于HTML结构,只有唯一一个要求,就是container的子元素只有一个(方便高度的获取,多子元素,高度计算麻烦多了)。

例如:

<div class="container">
    <p>我是孤独的根号3...</p>
</div>

或者:

<div class="container">
    <ul>
        <li>列表1</li>
        <li>列表2</li>
    </ul>
</div>

准备工作完毕,下面就是方法了(IE6~8浏览器直接设置height:auto即可):

var slideToggleTrans = function(element, display) { //  display表示默认更多展开元素是显示状态还是隐藏
    if (typeof window.screenX === "number") {
        // 现代浏览器
        element.addEventListener("click", function() {
            display = !display;
            var rel = this.getAttribute("data-rel"), eleMore = document.querySelector("#" + rel);    

            eleMore && (eleMore.style.height = display? (function() {
                var height = 0;
                Array.prototype.slice.call(eleMore.childNodes).forEach(function(child) {
                    if (child.nodeType === 1) {
                        var oStyle = window.getComputedStyle(child);
                        height = child.clientHeight + (parseInt(oStyle.borderTopWidth) || 0) + (parseInt(oStyle.borderBottomWidth) || 0);
                    }
                });
                return height;
            })() + "px": "0px");
        });
    } else {
        // IE6-IE8浏览器
        element.attachEvent("onclick", function() {
            display = !display;
            var rel = element.getAttribute("data-rel"), eleMore = document.getElementById(rel);
            eleMore && (eleMore.style.height = display? "auto": "0px");
            return false;
        });
    }
};

触发按钮,以及对应的显示隐藏元素,通过按钮元素上面自定义的”data-rel“属性相关联。
data-rel与显隐元素的id相关联 张鑫旭-鑫空间-鑫生活

您可以狠狠地点击这里:渐进使用transition的显隐公共方法demo

不支持transition的直接“啪啪”显示,支持transition的“呼呼”显示。渐进增强,简单又高性能。

需要额外说明的

  1. 以上公共的slideToggleTrans方法如果直接用在实际项目中,是有所欠缺的。缺在哪里呢?就是没有添加回调。因此,您可能需要添加一个额外的参数以及两行代码:
    var slideToggleTrans = function(element, display, callback) {
        // click事件中: ...; callback.call(element, eleMore, display);
    }
  2. 我们平时显隐更多地是通过display属性控制。然而,display无法触发transition动画。如果我们先设置display:block,再去改变高度,行也行。但是,这种感觉就像是:平时用地下水冲马桶的,后来自来水也能冲干净,但非要再用地下水冲一次。而且,在脚本的编写上,更折腾(display:none的元素无法获取子元素的真实高度)。
    还是那句话,为了让一切都显得那么简单,我们要稍微改变下习惯,学习使用height控制元素的显隐。
  3. 以上公共方法不依赖任何JavaScript库。我这里想说的不是称赞其灵活性,而是想说,我们平时开发,都应该有自己的JS框架的。因此,上述代码有必要在自己框架基础上进行一番整改,可以大大简单代码;同时提高参数的可定制性(data-rel, 回调等);以及其他相关扩展——如ajax更多加载的动画显示等!
    这里的,仅仅是最简单原始的,抛砖引玉而已。
  4. 定高其实是件伴随风险的事情,因为会有这样的情况:1. 页面内容宽度自适应,窄屏下元素应该更高;2. 更多元素内部还有类似展开收起的交互,高度可变。
    因此,我们还需要额外处理。情况1,需要在动画结束的时候,设置height: auto; 情况2则是,内部再出现2次交互的时候,再次改动元素的高度值(自动触发动画)。

七、这是一个平淡的结语

我脑中突然冒出两个名字:“社会框架”和“团队框架”。“社会框架”就像我们使用的完整版的jQuery,因为其要兼容千万开发者受众各种环境,各种使用情况,因此,变得庞大是在所难免的。但是,如果放在单一的团队的,很多很多的东西都是多余的。因为,团队中可以有一个名叫“规范”的东西进行约束。——比方说,这种UI交互,我们的HTML结构上需要注意什么,需要什么样的CSS配合就可以使用极简的JS代码实现我们想要的效果等等。

说点更实际的,本文的内容,如果要让我写一个面向万千使用者的transition动画插件,我需要考虑的就非常多:
1. 有的用户喜欢默认设置display:none, 以及其他CSS属性(例如其对容器设置了会冲突的overflow:auto);
2. 显隐元素里面就是文本,或者是列表子节点们。我需要获取各种情况下子元素们的高度。这很头疼;
3. 交互场景千千万。显隐元素内部还有其他域高度打交道的交互,我该如何处理;
4. 等等其他N多……

要全部兼顾上面的问题,可以想象,我的JS代码不是几十行就可以搞定了。肯定会有很多与核心逻辑不相干的代码,去处理这些个特殊情况。于是,洋洋洒洒N多代码,这是我很不喜欢的,大家也一定不会喜欢的。

但是,如果code只是面向一个规范的团队,或是自己内部小组,OK,我们就可以通过一些约定俗成,大大简化我们的代码,我们的开发,最后是大家都开心!

我越来越不喜欢过于肥胖的jQuery了!

原创文章,转载请注明来自张鑫旭-鑫空间-鑫生活[http://www.zhangxinxu.com]
本文地址:http://www.zhangxinxu.com/wordpress/?p=2717

(本篇完)

有话要说,点击这里发表评论。