使用css3新属性clip-path制作小图标

      一般一个网页上面,或多或少都会用到一些小图标,展示这些小图标的方法有很多种。最简单的做法就是将UI图上面的每个小图标都保存为图片,一个小图标就一张图片。但这也是比较笨的方法,因为浏览器同一时间最多加载的资源是有限的,例如IE7是2个,IE8是6个,chrome是6个,火狐是8个。如果网页上面有很多张零碎的小图片,导致请求的次数太多,等待加载状态中的资源会很多,明显影响性能。因此,一个改进的办法是使用sprites图,将多张小图放在一张大图,然后限定展示区域的大小,同时改变图片的显示位置background-position来显示不同的图标,游戏里面经常使用这种技术,大大减少浏览器请求的次数。淘宝网就使用这种技术:

      但是要看到这种方法也是有缺点的,即内存和CPU的使用增加,对于移动端低内存和CPU的设备来说,可能会有压力。使用sprites图,网上有很多在线的功具可以生成,同时会生成各个小图标的position位置,例如http://csssprites.com/

  第二种改进的办法是使用base64的编码方式。将原始二进制的图片编码为base64,然后使用css的background: url(data:image/png;base64,%encoding%)的方式,例如百度的首页搜索栏右边的话筒就是用这样的方式:

  将图片进行编码,可以使用在线工具base64 image,进行转换。转换之后,你会发现生成的编码特别长,其字节数甚至比原始的照片大,大约大33%。以上面的话筒为例,原始照片为1.3kb,而base64的编码需要1.7kb。同时,另外一个问题是对base64的解析速度比原始二进制的要慢。更严重的一个问题是,如果使用太多的base64,会使得css文件太大,下载和解析的时间较长,导致页面短时间的空白loading状态,效果可能还不如分开使用一张张图片。它的优点是不需要借用额外的图片文件,详细的分析可以看这篇文章

  第三种方法是使用CSS的技巧,这种方法一般只适用于比较简单的图案,例如三角形、五角星、爱心等。例如,如果想要画一个向上的三角形可以使用下面的方法:

.tri{
            width: 0;
            height: 0;
            border-left: 50px solid transparent;
            border-right: 50px solid transparent;
            border-bottom: 100px solid red;
}

  它的原理是将一个div的width和height设置成0,那就剩下四个border,四个角都是三角形,令其它三个角不显示,只留下底部那个角,就是一个向上的三角形。要注意设置左右角的宽度,目的是设置三角形上面两条边的长度,再将它们隐藏。更多CSS图形参考css shape。这种方法看似完美,因为无论是空间占用还是解析速度都比前面两个方法好,但是这种方式是不自然的,你无法轻易地改变图形的大小去适应你的页面,如果你不知道它画的原理是怎么样的。第二是无法容易地画出一些较为复杂的图案,例如为了画三个小黄人,花费了2000多行的CSS代码。另外一个缺点是,它是一个空的span或者div,对于屏幕阅读者来说是不可见的。

  第四种方法是使用icon font,将ui图里的icon导出制作成一个字体库,然后跟正常的字体一样使用,具体制作的方法可参考这篇文章。一般来说,icon font是从svg等矢量格式来的,通过PS导出png的方法可能会存在一些问题。boostrap的glyphicon就是使用icon font。使用时,先用@font-face导入字体(font-face的使用见这篇文章),然后利用一个span,设置font-family为刚刚导入的字体,再通过伪类before或after,属性content的值为对应图标的编码。或者是,直接在html文件里直接插入该图标的编码。如下所示:

  使用这种方法的优点是很大程序上减少了图片需要的空间,可以自由改变大小,改变颜色,支持IE6及以上。缺点是只适用于纯色的图标。手机淘宝和百度就使用了这种技术


   

      icon-font的制作方法可参见博主的另外一篇文章:把UI图里小图标制成icon font

  还有一种办法是使用Unicode字符,Unicode也提供了很多的图标和表情,例如打勾,✔ ✓ ☑,使用起来最为简单,可惜的是,不同的字体差别很大,有些字体没有这些符号,甚至是同一个字体在不同的设备上看起来也会有差异,例如✔在安卓机上的形状这是样的(中间的勾),而在ios上是这样的,同样都是使用了微软雅黑字体。

  上面提及的各种方法都存在一个缺点,没有语义性,都是一个空的span和div,对屏幕阅读者不可见。本文介绍一种新的画小图标的方法,使用svg结合css3的新属性clip-path。这种方法的优点是具备语义性,无论在性能还是占用的空间都具有优势。clip就是裁剪的意思,clip-path原本的用处是用来裁剪图片,如:

  上面,指定裁剪的路径为一个椭圆,x轴上的半径为裁剪区域的50%,y轴的半径为裁剪区域的40%,圆心在(50%, 50%)的位置。在这个椭圆形的封闭区域外的所有元素都不会被浏览器渲染出来,使用时要带有-webkit-前缀和标准的两种形式。Clippy这个网站可以在线裁剪,当前最新版本的chrome和safari都支持基本形状的裁剪。除了椭圆外还支持rect(长方形)、cirle(圆形)、inset(带圆角的长方形)、polygon(多边形),具体使用可结合上面的博客和网站进行探索。最后一种形式,是使用html里定义的svg元素作为裁剪的目标,这也正是clip-path的生命力所在。因为svg本身提供了丰富的语义定义,可以制作丰富多彩的矢量图形,更重要的是svg可进行可视化编辑,如AI,inkscape,还有一些在线的编辑器,如svg-editor。关于svg的基本介绍,可参考mdn的教程

  除了裁图片,利用clip-path的裁剪功能,可以用来制作图标。原理就是用一个div,设置background颜色和width/height值,然后制作一个图标的svg路径,用来裁剪div,就会显示出相应的小图标了。以打勾的图标为例

  首先,制作一个打勾的svg:

<svg width="0" height="0">
    <defs>
        <clipPath id="tick-mask" clipPathUnits="objectBoundingBox">
            <path fill="red" stroke="red" stroke-width="1" stroke-miterlimit="10" d="m0.1165671,0.4703638l0.0852069,-0.0852042l0.2337128,0.2335306l0.389592,-0.3894064l0.0852045,0.0852087l-0.4747964,0.4747913z" id="svg_8" clip-rule='evenodd'/>
        </clipPath>
    </defs>
</svg>

  注意这里,不是使用基本形状,而是使用了svg里的path,贝塞尔曲线,也就是PS/AI里面的钢笔工具,在d里面定义路径是如何移动和弯曲的。绘出的形状要放在clipPath标签里,给这个clipPath添加一个id,在下面的CSS里将会使用到,同时设置clipPathUnits为objectBoundingBox,作用是将单位设置成比例[0,1],这样就可以适配出不同大小的形状。clipPathUnits有两个取值,另外一个取值是userSpaceOnUse,是默认值,一般单位为px。

  形状画好了之后,由于要求背景是红色的,勾是白色的,因此先用一个div,设置红色背景和圆角,再用一个白底的span裁出一个勾的形状。如下:

<div class="icon">
    <span class='tick'></span>
</div>
.icon{
    width: 100px;
    height: 100px;
    background: #ff7443;
    text-align: center;
    background: #ff7443;
    border-radius: 100px;
}

.tick{
    display: inline-block;
    -webkit-clip-path: url(#tick-mask);
    clip-path: url(#tick-mask);  /* 在这里对白底的span进行剪切 */
    width: 90%;
    height: 90%;
    background: white;
    margin-top: 5%;
}

  这样就可以了。这篇文章作者作了一个圆形菜单,还有结合css3的动画,作了一些很有趣的动态效果。

  关于兼容性,IE和edge所有版本不支持clip-path,android的浏览器支持url参数的clip-path,但是UC和微信的内置浏览器不支持,微博的浏览器是支持的,firefox支持带url参数的。chrome支持-webkit-前缀的,包括基本的形状和url,safari/ios支持标准形式的,但是safari/ios在渲染上有bug,只要css文件里出现了clip-path,任何元素只要带position为relative/absolute的都会隐藏掉了,解决办法是,在这些元素里加多一个css属性:-webkit-transform: translateZ(0)加大渲染权重,这样就能显示出来了。还有可能会出现其它无法渲染的情况,例如,同一个id的clip-path只能渲染出第一个,接下来的都消失了,也可以用这种办法解决,但是如果渲染过重,在chrome等其它浏览器会出现显示的问题,会显示错乱。因此这个问题比较麻烦,h5开发的时候需要注意。

  对于无法支持的浏览器,改用其它的办法,得做个区分。可以借鉴modernizr提供的办法,页面加载时,首先创建一个svg和一个div,设置这个div的clip-path CSS属性,然后调用getComputedStyle看是否仍有刚刚设置的属性,如果有说明支持,没有说明不支持。如果支持就给body添加一个has-clip-path的类,不支持就为no-clip-path,然后在需要使用图标的元素的css前面加多一个clip-path的类,有和没有两个。这样就达到了区分的目的,不支持的就使用其它的方式。

<body>
    <svg style="display:none" width="0" height="0"><defs><clipPath id="_svg"><path d="M 0 0 L 0 0"></path></clipPath></defs></svg>
    <div style="-webkit-clip-path:url(#_svg);clip-path:(#_svg);display:none" id="_test"></div>
    <script>
        var style = document.defaultView.getComputedStyle(document.getElementById("_test"), null);
        var body = document.getElementsByTagName("body")[0];
        if(style.WebkitClipPath !== "url(#_svg)" && style.clipPath !== "url(#_svg")
            body.className = "no-clip-path";
        else
            body.className = "has-clip-path";
    </script>
   <!--body的其它元素--> </body>

  本来可以使用svg和clip-path做为h5开发,但是考虑到安卓上的某些国内浏览器不支持,以及safari让人头疼的渲染问题,所以就目前的情况来说应用到生产环境仍不太乐观。所以在PC的web端使用sprites图,在移动的h5端使用icon font并灵活结合其它方法。

  注意到,icon-font和clip-path本质都是一样的,都是使用了svg,只是使用的方式不同。因此在提供icon font图标的网站上,如icomoonfontello上,可将图标的svg制作字体,也可作为clip-path使用。

 

参考:

1. CSS vs. SVG: Shapes and Arbitrarily-Shaped UI Components 这篇文章比较了使用CSS和svg画图标的两种方法,强调了使用svg画图的优点。

2. SVG Tutorial,MDN一个关于svg的简明易懂的入门教程。

3. icomoonfontello,提供icon-font/svg小图标的网站。

4. Clippy在线操作clip-path

 

posted @ 2015-10-11 19:15  会编程的银猪  阅读(2575)  评论(1编辑  收藏