广告:SF 里弄了个 CSS 小圈子,欢迎一起来讨论问题

前端小图标处理方案众多,本文主要介绍基于雪碧图的处理方案,分析雪碧图的预处理和后处理模式的得与失,以及在项目中通常会遇到的问题以及解决方案。其他小图标处理方案未在此文探讨之列。

此外,本文更多的是理论性分析,具体技术实现不作深入解释。如有疑问,欢迎到上述小圈子一起讨论!

讨论的起点是工程工具 Gulp/Webpack 上的集成方案(手动拼合雪碧图的做法已经作古了)。

1. 预处理方案

代表:gulp.spritesmith 和 webpack-spritesmith

预处理方案是预先指定需要生成的雪碧图切片元素,由工具合成后,得到相应的雪碧图和数据 (S)CSS 文件,开发中将二者投入使用。

数据文件内容通常是可定义的,我们可以自定义模板或者内容生成函数(怎么写这里就不探讨了)。借助 SCSS 等 CSS 预编译语言的威力,如何使用雪碧图数据就极具便利性。

注意,下面的 SCSS 代码都是写的模板函数生成的内容,模板函数具体怎么写不在本文探讨范围内。

最简单的,我们可以直接生成类(如),如:

.icon-home {width: 12px;height: 12px;background: url(sprite.png) -56px -48px no-repeat;
}

这样在 HTML 结构中可以直接使用。但如果切片元素众多,每一条规则都要有单独的 background 属性的话,未免太冗长,稍微改进下,把公共的 background-image 属性提取出来(高清模式下还有 background-size,以下从简讨论),可以生成这样的内容:

.icon-home {width: 12px;height: 12px;background-position:-56px -48px;
}
.icon-back {width: 18px;height: 20px;background-position: -10px 0;
}
.icon-home,
.icon-back {background-image: url(sprite.png);
}

但是,如果有的类没用到,白白生成一个类岂不是很没必要?或者我们不想使用 .icon-home 这样子的类名,那我们用 SCSS 占位符可以解决这个问题:

%-icon-home {width: 12px;height: 12px;background-position:-56px -48px;
}%-icon-home,
%-icon-back {background-image: url(sprite.png);background-repeat: no-repeat;
}

然后需要用到的时候,再继承这个占位符:

.head-home {@extend %-icon-home;
}

这样,我们就能用上自定义的类名,并节省 CSS。

但如果遇到状态性变化怎么办?比如 :hover 时小图标变红,这时候其实除了 background-position 改变外,其他数据是没有变化的。

按上述方案,我们的写法会是:

.head-home {@extend %-icon-home;&:hover {@extend %-icon-home_hover;}
}

生成的最终 CSS 是:

.head-home {width: 12px;height: 12px;background-position:-56px -48px;
}
.head-home:hover {width: 12px;height: 12px;background-position: 0 0;
}
.head-home,
.head-home:hover {background-image: url(sprite.png);
}

思索发现,本质上占位符只是包含了图片信息,那我们换一种更加高端点的写法:

// 把雪碧图数据保存为一个 SCSS Map 数据
$__sprite__: ('home': ('width': 12,'height': 12,'x': 56,'y': 48,'url': 'sprite.png'),'home_hover': ('width': 12,'height': 12,'x': 0,'y': 0,'url': 'sprite.png')
);// 公共部分依然占位符
%-sprite-common {background-image: url(sprite.png);
}// 输出 background-position
@mixin sprite-position($name) {$data: map-get($__sprite__, $name);$x: map-get($data, 'x');$y: map-get($data, 'y');background-position: -#{$x}px -#{$y}px;
}// 输出其他切片元素私有数据
@mixin sprite-item($name) {// 继承公共样式@extend %-sprite-common;// 设置独有样式@include sprite-position($name);$data: map-get($__sprite__, $name);$width: map-get($data, 'width');width: unquote($width + 'px');height: unquote(map-get($data, 'height') + 'px');
}

生成出以上 SCSS 模板后,我们就可以这样使用雪碧图了:

.head-home {@include sprite-item('home');&:hover {@include sprite-position('home_hover');}
}

生成出来的效果类似于:

.head-home {background-image: url(sprite.png);
}
.head-home {background-position: -56px -48px;width: 12px;height: 12px;
}
.head-home:hover {background-position: -0px -0px;
}

这样,在 :hover 态下生成的 CSS 规则只会包含必要的变动。

总结起来,预处理模式的优点在于,通过自定义 CSS 数据文件,可以随心所欲地使用已经生成出来的雪碧图信息。

然而,在预处理模式下,开发的页面依赖的是生成后的雪碧图,而不是合并前的雪碧图切片元素,随之带来的问题是:没办法实现雪碧图的按需合并。

预处理方案一般以页面为单元组织雪碧图。思考这样的问题:如果一张雪碧图对应一个页面,那各页面的公共组件使用的雪碧图,是每个页面各一份副本,还是只保存一次切片元素,把通用的抽成一张公共雪碧图呢?

如果各存一份,公共组件的切片元素就得保存到多个文件夹,每次更新、删除、添加的时候得同步多处。如果管理不当,就会导致页面元素不同、废弃的切片仍被合并以及遗漏等问题。(以 CSS 文件为单元组织雪碧也会遇到类似的情况。)

如果抽成一张公共雪碧图,假设一个最简单场景:有 ABC 三个页面,其中 AB 页面有一个共同的切片元素 ab.png ,BC 页面也有一个共同的切片元素 bc.png。这两张切片元素都放进了 spr_common.png 中。由于静态资源管理需要,我们在打包的时候统一给资源加上签名,也就成了 spr_common.de353d.png。现在,需要更新 ab.png 这张切片,进而变成了 spr_common.5ef25d.png。而 C 页面的样式里包含了这张雪碧图,尽管自身没有任何变动,但由于公共雪碧图变了,C 页面的 CSS 也必须跟着变化。即,公共雪碧图会带来耦合问题,局部页面更新会造成其他页面不必要的跟随变化。

后处理方案则解决了这些问题。

2. 后处理方案

典型:postcss-sprites

后处理方案通过对已经生成的 CSS 文件进行分析,将其中包含切片元素的 backgroundbackground-image 作为依赖收集,合并成雪碧图后再将相关参数替换。

如上述典型工具,生成前是:

.comment {background: url(images/sprite/ico-comment.png) no-repeat 0 0;
}
.bubble {background: url(images/sprite/ico-bubble.png) no-repeat 0 0;
}

生成后是:

.comment {background-image: url(images/sprite.png);background-position: 0 0;
}
.bubble {background-image: url(images/sprite.png);background-position: 0 -50px;
}

如此一来,CSS 中有哪些切片元素就合并哪些,不会把没有用到的切片也合并进去。即一张 CSS 样式表有一张专门的雪碧图

不过,正如上面所看到的,后处理模式解决了按需合并的问题,也不会造成页面/组件间雪碧图的耦合,但却丧失了预处理方案中直接利用数据的便捷性。在预处理方案中,我们不用人工地去衡量切片元素的宽高,而是让 SCSS 自动输出,后处理方案却做不到这一点。

3. 预处理和后处理相结合

既要能像预处理那样不用人工的地去量切片,又要像后处理那样实现按需的合并,这就是我理想的开发模式。

基于以上探索,我写了个工具:postcss-sprite-property 来实现二者的平衡。做法是:

  1. 区分开发环境和生产环境。在开发环境中,不合并雪碧图,直接使用切片元素预览;生产环境中,为每一张样式表内的切片合并雪碧图
  2. 利用 node-sass 所支持的自定义函数功能,为 SCSS 注入 image-widthimage-height 两个自定义函数,用来查询图片宽高数据
  3. 使用 @sprite-item@sprite-position 两个混合,优雅地定义一张雪碧图元素
  4. 尽量为公共属性生成一个公共的规则
  5. Webpack 中,开发时不拼合雪碧图,让切片本身作为组件依赖;打包时合并雪碧图并剔除切片元素

来看一个实例:

我们把如下样式放进我们的公共样式库中:

// 使用雪碧图
// 以图片名作为参数
@mixin sprite-item($name) {// 统一一个路径存放切片元素// 这样就能得到图片的实际地址了$url: '../asset/sprite/#{$name}.png';// 注入的 `image-width` 函数可以帮我们查询图片宽度$width: image-width($url);@if (null == $width) {@warn 'Sprite element `#{$name}` not found!';} @else {// 获取图片高度$height: image-height($url);// 自动书写 `width` 和 `height`width: $width;height: $height;// 定义背景// 开发模式下直接按输出预览// 生产环境下将其替换为雪碧图的数据background: url($url) 0 0 / #{$width} #{$height} no-repeat;}
}
// 改变雪碧图 `background-position`
// 和上面的 `sprite-item` 区别仅在于
// 这个混合不输出 `width` 和 `height`
@mixin sprite-position($name) {$url: '../asset/sprite/#{$name}.png';$width: image-width($url);@if (null == $width) {@warn 'Sprite element `#{$name}` not found!';} @else {$height: image-height($url);background: url($url) 0 0 / #{$width} #{$height} no-repeat;}
}

现在,具体项目里就能这样使用了:

.home-head {@include sprite-item('pageA/home');&:hover {@include sprite-position('pageA/home_hover');}
}.home-back {@include sprite-item('pageB/back');
}

开发阶段,没有合成雪碧图,直接使用切片预览,所以效果是这样的:

.home-head {width: 12px;height: 12px;background: url("../asset/sprite/pageA/home.png") 0 0 / 12px 12px no-repeat;
}
.home-head:hover {background: url("../asset/sprite/pageA/home_hover.png") 0 0 / 12px 12px no-repeat;
}.home-back {width: 18px;height: 20px;background: url("../asset/sprite/pageB/back.png") 0 0 / 18px 20px no-repeat;
}

最后发布生成的样式则是这样的:

.home-head {width: 12px;height: 12px;background-position: -56px -48px;
}
.home-head:hover {background-position: 0 0;
}
.home-back {width: 18px;height: 20px;background-position: -10px 0;
}
.home-head,
.home-head:hover,
.home-back {background-image: url(sprite.png);
}

当然,如上所示,.home-head.home-head:hover 都在最后的公共规则中。最理想的当然是没有更好,但作为一个平衡方案,这仍是可接受的。

更具体的用法和更多的功能请参看 Github:https://github.com/HaoyCn/pos...

4. REM 布局中雪碧图的错位问题

这个算是雪碧图的硬伤。其他小图标方案不会有这个问题。REM 布局里雪碧图错位几乎是不可避免的,在安卓下错位甚至可以达到 2px 左右。我也总结出一个 CSS 兜错方案:

  • background-position 使用百分比单位
  • 给雪碧图增加透明内边距,如 spritesmith 工具设置 padding: 8
  • 增加 1-2px 的容错区域,概要代码如下:

    %-sprite {// 错位时的容错区域padding: 1px;// 从内容区开始绘制背景background-origin: content-box;// 在内边距盒外裁切背景background-clip: padding-box;
    }

    利用以上三个属性的组合来腾出容错的空间。

4. 结语

关于雪碧图的处理方案的讨论就到此结束了。待 HTTP2 普及之后,也就没有雪碧图什么事了,而现在仍有其存在的必要性。

处理小图标还有其他的方案,如 Iconfont 和 Svg-Sprite。总之没有最好的,只有最适合的。

感谢阅读。欢迎来文章顶部的小圈子一起讨论!

CSS 关于雪碧图预处理和后处理方案的讨论相关推荐

  1. 猿人学题库十九题——css加密-雪碧图/数据干扰等——地毯式采坑学习

    猿人学题库十四题--css加密-雪碧图/数据干扰等 1.  首先 进入 浏览器的开发者工具, 打开就是 俩个无线debugg,正常的操作过掉 1.  找到 debugg 对应的行数,右击选择 neve ...

  2. JAVA自动生成雪碧图sprites和样式CSS文件(包含原始图标CSS、雪碧图CSS)

    在项目的开发过程中,如果一个页面有很多的小图标展现.浏览器展示页面时会向后台服务器发送很多的请求获取对应的图片,这样既浪费资源,也使得页面的加载变得很慢,影响客户的体验.此时我们可以采用将这些小图标放 ...

  3. CSS Sprites的原理(图片整合技术)(CSS精灵)/雪碧图

    CSS Sprites的原理(图片整合技术)(CSS精灵)/雪碧图   一.将导航背景图片,按钮背景图片等有规则的合并成一张背景图,即将多张图片合为一张整图,然后用background-positio ...

  4. CSS Sprite雪碧图详解

    CSS雪碧图,即CSS Sprite,也有人叫它CSS精灵图,是一种图像拼合技术.该方法是将多个小图标和背景图像合并到一张图片上,然后利用CSS的背景定位来显示需要显示的图片部分. 雪碧图的使用场景 ...

  5. 在html中雪碧图的坐标怎么看,详解CSS Sprite雪碧图的应用

    CSS雪碧图,即CSS Sprite,也有人叫它CSS精灵图,是一种图像拼合技术.该方法是将多个小图标和背景图像合并到一张图片上,然后利用CSS的背景定位来显示需要显示的图片部分. 雪碧图的使用场景 ...

  6. css sprit雪碧图制作,使用教程

    写在前面: 在网页制作中,雪碧图也是前端攻城狮必须掌握的一项小技能.百度词条对雪碧图的解释是:CSS雪碧 即CSS Sprite,也有人叫它CSS精灵,是一种CSS图像合并技术,该方法是将小图标和背景 ...

  7. css 雪碧图计算方式,前端必备 CSS Sprites雪碧图生成工具

    [摘要] CSS Sprites又称css精灵或者谐音css雪碧,是一种网页图片应用处理方式,今天为大家分享一个一键生成css雪碧图片的工具,复杂的事情简单化,提高开发效率. CSS Sprites又 ...

  8. CSS Sprite——雪碧图

    CSS Sprites技术被国内一些人称为CSS雪碧图,其实就是把网页中一些背景图片整合到一张图片文件中,再利用CSS的"background-image","backg ...

  9. 【HTML+CSS】(2)CSS Sprite雪碧图

    1. 雪碧图的使用场景 (1). 静态图片.不随用户信息的变化而变化 (2). 小图片.图片容量比較小 (3). 载入量比較大 一些大图不建议拼成雪碧图,比如淘宝站点的导航图片都是使用的雪碧图. 2. ...

最新文章

  1. UI设计要做什么,UI设计培训都要学什么
  2. Linux文件夹共享(NFS)
  3. swift_000(Swift 的导读必看)
  4. bootstrap中图片的一些小事情
  5. 《图解深度学习》图书及代码,16章带你无障碍深度学习
  6. VC++显示文件或文件夹属性
  7. Flexsim——初学AGV必看的知识点(如何解决AGV在不同区域speed不同)
  8. 【智慧医疗】破解医疗数据孤岛,实现信息共享
  9. 图像拼接算法的基本原理
  10. IOT是什么?有哪些用途和技术?
  11. 数字化时代,银行如何建设管理小程序平台促进线上金融业务发展?
  12. 用Python验证指数基金定投策略
  13. 学习笔记 第八周 第二篇(修改版)
  14. msm8916 lcd 相关调试点指导
  15. 杭州卧兔全球首发2022海外网红营销白皮书
  16. 软件测试主管应具备的素质,软件测试人员应具备的素质
  17. Matlab画x=a,y=b直线
  18. caffe 画损失曲线和准确率曲线
  19. 聚焦循环经济商业模式 ROEHL亮相联合国气候变化大会COP26中国企业馆
  20. VMware VCSA 7.0 Install

热门文章

  1. 常考数据结构与算法:最长回文子串
  2. spring boot三:spring boot的hello, world
  3. smartClient 2--可视化组件
  4. 阿里云系列——4.网站备案后续(详细步骤)---2015-11.12
  5. 6、Gerrit插件
  6. [数据结构] 希尔排序
  7. 为 Visual Studio 安装数据库工具
  8. Android 消息推送
  9. 安装vsftpd-3.0.2.tar.gz源码
  10. Intel Developer Forum 2010英特尔信息技术峰会第二天小记