当前位置: 首页 > Web前端 > CSS

CSS-Sprite图片前处理和后处理方案讨论

时间:2023-03-31 11:52:06 CSS

广告:SF有CSS小圈子,欢迎一起讨论问题前处理和后处理方式的得失图表的内容,以及项目中经常遇到的问题和解决方法。其他小图标处理方案不在本文讨论。另外,本文更多的是理论分析,具体的技术实现不做深入讲解。有什么问题欢迎到上面小圈一起讨论!讨论的起点是工程工具Gulp/Webpack上的集成方案(手动拼接精灵的做法很古老)。1.代表性预处理方案:gulp.spritesmith和webpack-spritesmith预处理方案预先指定需要生成的sprite图像切片元素。通过工具合成后,得到相应的精灵图片和数据(S)CSS文件。两个投入使用。数据文件的内容通常是可以定义的,我们可以自定义模板或者内容生成函数(具体怎么写这里不讨论)。借助SCSS等CSS预编译语言的强大功能,如何使用精灵数据就非常方便了。注意下面的SCSS代码是编写模板函数生成的内容,模板函数怎么写不在本文讨论范围之内。最简单的,我们可以直接生成类(eg),比如:.icon-home{width:12px;高度:12px;background:url(sprite.png)-56px-48pxno-repeat;}在HTML结构中可以直接使用。但是,如果切片元素很多,每条规则都必须有一个单独的背景属性,这样就太冗长了。稍微改进一下,提取出publicbackground-image属性(background-size在高清模式下也有,下面会简单介绍)。可以生成这样的东西:.icon-home{width:12px;高度:12px;背景位置:-56px-48px;}.icon-back{宽度:18px;高度:20px;背景位置:-10px0;}.icon-home,.icon-back{background-image:url(sprite.png);}但是,如果有些class没有用到,那白生成一个class岂不是没必要?或者我们不想使用.icon-home这样的类名,那么我们可以使用SCSS占位符来解决这个问题:%-icon-home{width:12px;高度:12px;背景位置:-56px-48px;}%-icon-home,%-icon-back{背景图像: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;高度:12px;背景位置:-56px-48px;}.head-home:hover{width:12px;高度:12px;background-position:00;}.head-home,.head-home:hover{background-image:url(sprite.png);}想了想,我们发现placeholder本质上只包含图片信息,所以我们换一种更高端的写法://将Sprite图像数据保存为SCSSMap数据$__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@mixinsprite-position($name){$data:map-get($__sprite__,$name);$x:map-get($data,'x');$y:map-get($data,'y');background-position:-#{$x}px-#{$y}px;}//输出其他切片元素的私有数据@mixinsprite-item($name){//继承通用样式@extend%-sprite-common;//设置独特的风格@includesprite-position($name);$data:map-get($__sprite__,$name);$width:map-get($data,'width');宽度:取消引号($width+'px');height:unquote(map-get($data,'height')+'px');}上面的SCSS模板生成后,我们可以这样使用Sprite图片:.head-home{@includesprite-item('家');&:hover{@includesprite-position('home_hover');}}生成结果效果类似于:.head-home{background-image:url(sprite.png);}.head-home{background-position:-56px-48px;宽度:12px;height:12px;}.head-home:hover{background-position:-0px-0px;}这样,在:hover状态下生成的CSS规则将只包含必要的变化使用生成的Sprite贴图信息。但是在预处理方式下,开发页面依赖于生成的sprite图片,而不是合并前的sprite图片切片元素,这就带来了无法实现按需合并sprite图片的问题。预处理方案一般以页面为单位组织精灵图像。想一想这个问题:如果一个sprite图片对应一个页面,那么每个页面的公共组件使用的sprite图片是不是每个页面都复制一份,还是只保存一个slice元素,将一般的提取成一个publicspriteimage毛呢布?如果每个保存一份,那么公共组件的切片元素就得保存在多个文件夹中,每次更新、删除或添加时都需要同步多个地方。如果管理不当,可能会导致页面元素不同、过时的切片仍在合并、遗漏等问题。(以CSS文件为单位组织Sprite时也会遇到类似的情况。)如果绘制公共Sprite图片,假设最简单的场景:ABC三个页面,AB页面有一个公共切片元素ab.png,BC页面也有一个公共切片元素bc.png。两个切片元素都放入spr_common.png中。由于静态资源管理的需要,我们在打包的时候统一给资源加上签名,变成spr_common.de353d.png。现在,ab.png切片需要更新为spr_common.5ef25d.png。C页面的样式包含此sprite图像。虽然本身没有变化,但是既然公开的sprite图片变了,那么C页的CSS也必须随之变化。即公共精灵图会带来耦合问题,本地页面更新会引起其他页面不必要的后续变化。后处理解决方案解决了这些问题。2、典型的后处理方案:postcss-sprites后处理方案对生成的css文件进行解析,收集背景或包含slice元素的background-image作为依赖,合并成sprite图片,然后替换相关参数。像上面典型的工具一样,在生成之前:no-repeat00;}生成后为:.comment{background-image:url(images/sprite.png);background-position:00;}.bubble{background-image:url(images/sprite.png);background-position:0-50px;}这样CSS中那些slice元素会被合并,没有使用的slice不会被合并。也就是说,一个CSS样式表有一个专用的sprite图像。但是,如上所见,后处理方式解决了按需合并的问题,不会造成页面/组件之间sprite图片的耦合,但失去了前处理方案中直接使用数据的便利性。在前处理方案中,我们不需要手动测量切片元素的宽高,而是让SCSS自动输出,而后处理方案做不到这一点。3.前处理与后处理的结合,既要能像前处理那样人工测量、切片,又能像后处理一样实现按需合并。这是我理想的开发模式。基于以上探索,我写了一个工具:postcss-sprite-property来达到两者的平衡。做法是:区分开发环境和生产环境。开发环境下,不合并精灵图,直接预览切片元素;在生产环境中,为样式表中的每个切片合并sprite图片,使用node-sass支持的自定义函数function将image-width注入到SCSS中,image-height和image-height是两个自定义函数用于查询图片的宽高数据。使用@sprite-item和@sprite-position优雅地定义精灵元素。尝试在Webpack中为公共属性生成一个公共规则,开发时精灵图不被压扁,切片本身作为组件依赖;打包时合并精灵图片,去掉切片元素看例子:我们将如下样式放入我们的公共样式库中://使用精灵图片//以图片名称作为参数@mixinsprite-item($name){//统一一个存放切片元素的路径//这样会得到图片的实际地址$url:'../asset/sprite/#{$name}.PNG';//注入的`image-width`函数可以帮助我们查询图片宽度$width:image-width($url);@if(null==$width){@warn'Sprite元素`#{$name}`未找到!';}@else{//获取图片高度$height:image-height($url);//自动写入`width`和`height`width:$width;身高:$身高;//定义背景//开发模式直接按输出预览//生产环境替换为雪碧图背景的数据:url($url)00/#{$width}#{$height}不重复;}}//更改精灵图像`background-position`//与上面的`sprite-item`的区别仅在于//这个mixin不输出`width`和`height`@mixinsprite-position($name){$url:'../屁股et/sprite/#{$name}.png';$width:图片宽度($url);@if(null==$width){@warn'未找到Sprite元素`#{$name}`!;}@else{$height:图片高度($url);背景:url($url)00/#{$width}#{$height}不重复;现在,这可以在特定项目中完成使用:.home-head{@includesprite-item('pageA/home');&:hover{@includesprite-position('pageA/home_hover');}}.home-back{@includesprite-item('pageB/back');}开发阶段没有合成精灵图,直接使用切片预览,所以效果是这样的:.home-head{宽度:12px;高度:12px;背景:url("../asset/sprite/pageA/home.png")00/12px12pxno-repeat;}.home-head:hover{background:url("../asset/sprite/pageA/home_hover.png")00/12px12px无重复;}.home-back{width:18px;高度:20px;background:url("../asset/sprite/pageB/back.png")00/18px20pxno-repeat;}lastpost生成的样式是这样的:.home-head{width:12px;高度:12px;背景位置:-56px-48px;}.home-head:hover{background-p位置:00;}.home-back{宽度:18px;高度:20px;background-position:-10px0;}.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布局中Sprite贴图错位这是Sprite贴图的缺陷。其他小图标方案没有这个问题。REM布局中sprite图片的错位几乎是不可避免的,Android下甚至可以达到2px左右的错位。我也总结了一个CSS错误的解决方法:background-position使用百分比单位给Sprite图片添加透明内边距。比如spritesmith工具设置padding:8来增加1-2px的容错区域。大致代码如下:%-sprite{//错误对齐时的容错区域padding:1px;//从内容区域绘制背景background-origin:content-box;//裁剪内边距框外的背景background-clip:padding-box;}将以上三个属性结合起来,为容错腾出空间。4.结语Sprite图像处理方案的讨论到此结束。HTTP2普及后,就没有Sprite图这种东西了,还是有存在的必要的。还有其他处理小图标的解决方案,例如Iconfont和Svg-Sprite。总之没有最好,只有最适合。谢谢阅读。欢迎来到文章顶部的小圈一起讨论!