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

这篇文章发布于 2012年10月19日,星期五,15:54,归类于 Web综合。 阅读 145280 次, 今日 5 次 30 条评论

 

一、这是个平淡的开头

在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了!

(本篇完)

分享到:


发表评论(目前30 条评论)

  1. KJ说道:

    旭哥,以案例一为例,在图片加上一个padding,则展开收齐的时候会显示一个padding的图片高度,如何解决这个问题

  2. 错别字说道:

    不能属性值的应用前后必须有时间差,例如:
    第六节,transition的实现方法下面“不能属性值的应用前后必须有时间差,例如:”
    是不同吧,鑫哥

  3. kerry说道:

    请问,如何做到内容不从上面推下来?希望能像 Apple.com 那样的:
    http://www.apple.com/cn/shop/buy-iphone/special-edition-iphone-7
    底部的「常见问题解答」展开后的小问题,继续点击,则可以看到效果~

  4. YDiego说道:

    请教 eleMore && (eleMore.style.height = display? “auto”: “0px”); 这段代码中&&是什么用法

  5. 飞行荷兰豆说道:

    博主为毛什么例子都用这美女的图片…….换海绵宝宝多好

  6. yhl452493373说道:

    css3动画确实流畅,就是要算高度啊啊啊啊。。。。。不知道楼主有没有分析jquery的slidedown的源代码的文章,了解到jq算高度的方法就好了

  7. 阿磊说道:

    bootstrap 里面的实现 就是你所说的 鸡肋,他使用动画事件来监控,让我觉得很不爽。这个高度问题 的确很扯,你这12年的文章。我现在才遇到你当时的问题。不过你的职业和我的爱好都一样不得不说是缘分。 大哥你真够超前

  8. Cc说道:

    楼主,我想到另一个方法:
    先设置display:block,延迟10ms左右再改变“透明度”。
    这样不知可否?不用计算高度,js估计也会少一点吧?(个人不是很懂js。。。)求评价!跪谢~

  9. 杨勇说道:

    slide 在iPad上反应很慢,明显很卡,请问有好的解决方法吗

    • 张 鑫旭说道:

      @杨勇 1. 绝对定位 2. 浅dom 3. 独立少关联 4. 去除半透明以及盒阴影等渲染,纯色底纹。
      注:iPad不是杂牌国产pad,速度可以的。

  10. Alex说道:

    query确实是越来越庞大了。如果要分析源码还是推荐zepto.js。 并且slide在ie下表现非常糟糕。

  11. sp42说道:

    楼主好是好,可是太多字鸟……看得俺的眼……

  12. FE_lu说道:

    想知道在IE6 图片不显示的bug 楼主是怎么解决的。。我也遇到这个。怎么改都不显示。。拷贝楼主的链接进IE6又是好的。 又全拷贝楼主代码 发现还是不显示。。

  13. java_ljy说道:

    楼主 可以不可以也多分享下你的学习javascript 啊 你现在分享的CSS 也很全面和详细 哈

  14. java_ljy说道:

    ie6 有BUG 图片显示不出来。其他浏览器完美哈!

  15. 看远说道:

    当单位不一致的话,貌似也不会有动画,例如height:40px → height: 80%,博主共享下阉割掉的jQuery吧,学习下。

  16. stone说道:

    博主那首诗真的很不错,好诗!好诗!

  17. cylmoon说道:

    博主研究的真深刻,我只遇到过jquery silide的滑入划出效果确实有停顿,确没有究其根源,博主的精神值得我学习呀,

  18. hgh说道:

    敢问帮主 有研究过css3的图片滚动么 就是淘宝网的触屏版的图片滚动 期待此问题的博文

  19. _t说道:

    because 我们有时希望用半透明效果, display 是必须的啦 – 否则它透明了也点不到底下的东西。。。

  20. _t说道:

    相当好的~ IE 10 支持不带有前缀的 transition 效果超赞。。。另外最近 Firefox 也支持不带有前缀的 transition 版本了~

  21. yetazhan说道:

    最近css3好多啊 不过我最近也研究css3,还挺有收获的,
    版主什么时候讲讲原生的js些框架之类的话题
    挺了

  22. 牛逼的博主说道:

    如果页面有多个需要更多收起,怎么办?