×

【SVG】SVG图标非常方便,但降级并不容易

作者:Terry2016.09.10来源:Web前端之家浏览:11699评论:0
关键词:html5SVG

图标字体的使用在慢慢减少。最近很多人找出了不要使用字体图标的理由,并劝大家使用SVG图像。在《金融时报》(注:作者是一名《金融时报》的前端工程师),我们一致认为,总的来说,是时候探索作出一些转变了。

SVG的浏览器支持很好;超过94%的浏览器完全兼容SVG 1.1规范,如果你不需要使用mask的话,这个比例可以提高到96%。

根据具体情况,你可以权衡一下,是否为剩下的4%的浏览器提供降级,但是要考虑到对于你的网站访客来说,能看到图标是非常重要的。关于糟糕的用户体验的讨论,很多网站都依赖于使用图标来导航、切换重要的菜单、关闭提示或执行重要的操作日志等。

SVG图标非常方便,但降级并不容易

在空间受限的地方,经常会使用图标。用户必须依靠这些来执行关键操作。

《金融时报》的图标集是Origami的一部分——《金融时报》的品牌标志,以及Web开发工具——必须支持公司的广泛使用,而且使用简单并提供可靠的、健壮的支持。因为《金融时报》有非常多的用户量,所以针对这4%的不支持SVG的降级就尤为重要。

事实证明,降级做起来并没有想象中那么容易。

模板

实现SVG图标有很多种方法——内联元素,图像或背景图像。因为需要提供一个坚实的降级,所以选择如下:

  • 内联<svg>元素的降级太复杂。一个普通的<img>元素需要依赖srcset,IE/Opera Mini/旧版安卓系统的浏览器不支持。这样每个图标的降级都需要一次单独的请求。✘

  • 背景图像从2000年初就在使用了,用来实现sprite和升级字体图标的项目,这样可以不需要全盘改写代码。✓

旧版基于图标的网页字体是根据符号需要,通过添加一个基本class和变量class来实现。作为字体它将继承父元素的颜色和尺寸。基于sprite的背景图像方法不能继承,所以需要额外定义其颜色和尺寸:

<!-- current font based implementation 当前字体的基本实现方法 -->
<span class="icon icon--{symbol}"></span>

<!-- proposed future implementation 将来可能的趋势 -->
<span class="icon icon--{size} icon--{colour} icon--{symbol}"></span>

Sprite表

降级图像非常重要,使用SVG片段标识符不是一个选择。我需要给每个图标一个常规的、不重叠的布局,就像我们在2004年做的那样。

每个我试过的SVG sprite工具可以输出一个sprite表,统一颜色,无论是从源逐字复制,还是引入一个设置来覆盖它。我真的很想避免针对20+个调色板,每种颜色都生成一个单独的文件,所以我们必须推出自己的系统。

在《金融时报》我们有很多符号和前面提到的大调色板,所以我们认为强制每个网站用户都下载一个大sprite sheet是不公平的,如果这个产品只使用几个图标。我写了一个小应用程序,可以生成一张sprite sheet,只包含请求的符号和颜色。

这个应用程序是通过提取源SVG文件的路径,进行一些优化,然后把它们作为定义包含在输出文件中。每个定义都有一个viewBox计算,这样它们占据的空间是相等的。定义对它们的产品本身不可见,所以程序遍历请求的颜色,引用每条路径,然后应用正确的填充。

SVG图标非常方便,但降级并不容易

SVG sprite生成器把路径输出到单独的源SVG文件,优化并计算viewBox,以使它们占据相等的空间。程序遍历请求的颜色和引用每条路径,并应用填充。

现在我们只使用系统来生成静态文件——包括SVG和PNG——但是我们日后可能实现一些类似的,如即时sprite sheet生成,如果我们看到这方面的需求。

样式

这是最初的Sass代码。你可能会有点疑惑,我后面再解释。颜色和符号都被包含字sprite sheet中,连同显示的尺寸变量。

实例都是使用Sass来编写的,为了更清楚地展示所需的计算,已经调整为清晰可观看的状态。

$icon-sizes: (16, 24, 36, 48);
$icon-colors: ('black', 'white', 'blue', 'red');
$icon-symbols: ( 'chronometer', 'cog', 'hearts', 'rocket', 'sign', 'speech', 'user');

.icon {
  position: relative;
  display: inline-block;
  overflow: hidden;

  &:after {
    content: '';
    position: absolute;
    width: percentage(length($icon-colors));
    height: percentage(length($icon-symbols));
    background-image: url('sprite-sheet.svg');
    -webkit-background-size: 100%;
    background-size: 100%;
  }
}

我没有使用sprite sheet作为.icon元素的背景图像可能有点奇怪,而是增加伪元素的复杂性。这是因为sprite sheet使用从左到右带颜色的坐标轴,而符号是从上到下,而background-position不能单独指定一个坐标轴。

单独使用背景定位需要为每一个图标+颜色组合定义单独的样式——这是一个额外的针对《金融时报》的50个符号和20种颜色的1000条声明。

对伪元素使用绝对定位,在所有浏览器中都是可以工作的。而leftright属性可以指定单独的类名;icon--{colour}对于水平轴,.icon--{symbol}对于垂直方向。

background-position-xbackground-position-y属性在浏览器中同样是支持的,但是它们目前不是CSS规范的一部分,所以没有普遍实现。属性已经提出在CSS 4的backgroundborder模块中加入。

如果应用背景图像是可行的,但仍然需要进行缩放,这样每个图标才可以填充容器元素,而不管其本身的大小。sprite sheet分为常规、同样大小的正方形。sprite sheet中的颜色和符号数量都了解了所以缩放是可计算的——一般是100%(元素的宽)x 颜色/符号的数量。然而,不是使用这些值来设置background-size,它们用来设置伪元素的大小,这样sprite sheet可以设置为合适的尺寸。

SVG图标非常方便,但降级并不容易

sprite sheet分为常规,相同尺寸的正方形,sprite sheet中颜色和符号的数量已知,这样才可以计算缩放。

所以,设置部分完成了,但对于自身不是非常有用。需要Class来指定大小、颜色、要展示的符号。这些参数定义在列表中,可以被遍历来自动生成每个声明:

@each $size in $icon-sizes {
  .icon--#{$size} {
    width: $size + px;
    height: $size + px;
  }
}

@each $color in $icon-colors {
  .icon--#{$color} {
    &:after {
      $color-index: index($icon-colors, $color);
      left: percentage($color-index - 1) * -1;
    }
  }
}

@each $symbol in $icon-symbols {
  .icon--#{$symbol} {
    &:after {
      $symbol-index: index($icon-symbols, $symbol);
      top: percentage($symbol-index - 1) * -1;
    }
  }
}

对于支持SVG的浏览器,到这里就完成了!你可以查看codepen上的demo

现在麻烦的部分到了....

降级

不支持SVG的浏览器大致可以分为两组:旧IE(<IE 9),以及安卓旧浏览器。所有这些浏览器都需要一个栅格图像降级,这可不是简单地指定两个背景图像,它们必须阻止下载以及尝试应用SVG文件。

这是通过不可见的渐变方法来代替SVG支持实现的。它并不完美(IE9和Android 3的旧浏览器不支持SVG,会被淘汰的),但也算好的了:

.icon:after {
  background-image: url('sprite-sheet.png');
  background-image: -webkit-linear-gradient(transparent, transparent),  
    url('sprite-sheet.svg');
  background-image: linear-gradient(transparent, transparent),
    url('sprite-sheet.svg');
}

旧的Android浏览器工作没问题,是时候解决旧版IE和其它不支持CSS3背景选项的浏览器的问题了。

选择一——颜色——为每个尺寸变量提供一个单独的sprite sheet。可以提前生成或根据需求提供sprite sheet,通过一个图像服务,但这种技术也意味着额外的请求,也会出现问题,只针对需要它的浏览器。这个选择只有在你愿意忍受这些缺点的时候才是可行的。

在旧版IE中有ActiveX过滤器,AlphaImageLoader有一个sizingMethod选项,可以用来模拟background-size: 100% 100%。不幸的是过滤器不能应用在伪元素上,如此古怪,所以hack这时候也没用了。

我使用另一个非标准的属性解决了这个问题,zoom。因为icon元素必须给定尺寸,sprite sheet才可以按照已知的尺寸和比率生成。这意味着icon元素不能给定一个相对尺寸,但是我想这是对周围工作的一个小警告。

有点麻烦的是,元素的绝对定位必须按照sprite sheet的原生尺寸重新计算,这些属性也会被放大。

zoom可能不是CSS规范的一部分但是它已经实现得相当广泛了。最新版本的Chrome中的降级和IE8中的一样。

每个降级属性都被重写,通过包装在媒体查询中的降级。带有CSS3背景选项的媒体查询的支持是非常安全的。

$icon-intrinsic-size: 50;

@each $size in $icon-sizes {
  .icon--#{$size} {
    width: $size + px;
    height: $size + px;

    &:after {
      zoom: 1 / $icon-intrinsic-size * $size;

      @media all and (min-width: 0) {
        zoom: 1;
      }
    }
  }
}

@each $color in $icon-colors {
  .icon--#{$color} {
    &:after {
      $color-index: index($icon-colors, $color);
      left: $icon-intrinsic-size * ($color-index - 1) * -1 + px;

      @media all and (min-width: 0) {
        left: percentage($color-index - 1) * -1;
      }
    }
  }
}

@each $symbol in $icon-symbols {
  .icon--#{$symbol} {
    &:after {
      $symbol-index: index($icon-symbols, $symbol);
      top: $icon-intrinsic-size * ($symbol-index - 1) * -1 + px;

      @media all and (min-width: 0) {
        top: percentage($symbol-index - 1) * -1;
      }
    }
  }
}

总结

我想你们大多数人不会面临像《金融时报》的Origami团队这样的需求。如果你不需要一个非SVG的降级,或你的图标尺寸都是单独的,那么SVG sprite是最直接的实现图标字体的方法。但是,考虑到如此严格的要求,我们已经推出了beta实现,希望收集尽可能多的反馈,我也会适时地在这里更新。我想回答的问题是:

  • 它会比提供一个高分辨率的PNG更高效吗?

  • 在一个页面上使用很多SVG图标是否会有性能问题?

  • 字体图标的缺点是否大于实现SVG图标的复杂度(包括适当的降级)?

最后效果如下:

您的支持是我们创作的动力!
温馨提示:本文作者系Terry ,经Web前端之家编辑修改或补充,转载请注明出处和本文链接:
https://jiangweishan.com/article/svg1473436800792.html

网友评论文明上网理性发言 已有0人参与

发表评论: