移动端适配之雪碧图(sprite)背景图片定位

为了减少网络请求个数量,提高网站的访问速度,我们一般都会把一些小的图片合并成一张sprite图,然后根据background-position来进行定位。在web端由于是固定的大小与left 、top,所以定位起来会比较准确、简单。但是在移动端就不一样了,各种手机的屏幕大小不一样,很难做到使用sprite图然后根据background-position来定位。所以普遍的做法都是使用单张图片,然后使用background-size: cover|100%|contain来控制背景图的大小。其实这样会简单得多,但是呢,如果图片太多,网速不好的情况下加载速度就惨不忍睹了。所以,根据之前的移动端适配之rem找到了解决方案。如果没有看过之前的文章,还是建议去看一下。

还是以视觉稿为 640px为例,这是视觉稿的一部分:

移动端视觉稿

根据这个视觉稿,我切出来合并的sprite图张这样:

原始sprite图 1072*442

这是640px视觉稿切出来的图,如果只是适配640px的屏幕,直接使用px定位完全没有问题,但是考虑到其他的屏幕,所以我们会使用rem来等比例缩放背景图。是的,把原尺寸转换为rem就可以了。代码如下:

html代码结构
    <div class="test-sprites">
        <ul class="f-cb">
            <li class="icon1"></li>
            <li class="icon2"></li>
            <li class="icon3"></li>
            <li class="icon4"></li>
            <li class="icon5"></li>
            <li class="icon6"></li>
        </ul>
    </div>

sass 代码

.test-sprites{
    margin-top: 30px;

    ul{
        padding: 0;
        margin: 0;
    }

    li{
        width: 0.48rem;
        height: 0.7rem;
        overflow: hidden; 
        border: 1px solid #ccc; 
        margin-left: 0.3rem;
        float: left;
        background:transparent url('http://nos.netease.com/edu-image/9BC0742AEB1A0B756EFC71B9DF77E452.png') 0 -0.02rem no-repeat;
        background-size: 10.72rem 4.42rem;
    }

    .icon2{
        width: 0.74rem; 
        height: 0.64rem;
        background-position: -1.88rem -0.05rem;
    }

    .icon3{
        width: 0.71rem;
        height: 0.74rem;
        background-position: -3.91rem -0.02rem;
    }

    .icon4{
        width: 0.72rem;
        height: 0.73rem;
        background-position: -5.91rem -0.03rem;
    }

    .icon5{
        width: 0.73rem;
        height: 0.73rem;
        background-position: -7.92rem -0.01rem;
    }

    .icon6{
        width: 0.67rem;
        height: 0.57rem;
        background-position: -9.96rem -0.08rem;
    }
}

我们平常使用background-size: 100%|cover|contain只是根据元素的宽高进行缩放的,那只对单张图片有用,因为此时百分比的大小是相对于元素的大小的,也就是说,一个100px*100px的div,使用一张1000px*1000px的sprite图做背景图片的,如果此时你给div加上background-size: 100%|cover|contain的话,那么整张sprite图就会被压缩成100*100的大小,这恐怕不是你想要的吧。

而我们的sprite是要根据它的原始大小来进行缩放的,先把px转化为rem,按照我的习惯是直接除以100px。也就得到类似background-size: 10.72rem 4.42rem;(原始1072px*442px)。这样sprite图就可以根据font-size进行缩放了。而且定位background-position直接使用原始的left-top 值除以100px就可以了,就是那么简单,BB了那么多,见证奇迹的时候到了。那我们直接上效果图:

适配结果.png

看,是不是感觉适配得还不错。对的,大体上是可以了,但是呢,认真看看有一些手机里面,总会发现有点缺陷,有些图片少了那么1px,其实如果要求不严格的话这样也就差不多了。但是本着精益求精的原则,我们肯定是需要解决这个问题的。于是在网上查找,功夫不负有心人,找到了林小志_linxz的这么一篇文章
其中比较关键的是:

属性值为百分比时,将以图片的 中心点 为基准计算其相对位置,而使用px像素值时将以图片的 左上角(0 0)为基准。如果是10% 20%的这个值,那么就以图片的10% 20%的坐标点,放置在容器的10% 20%的位置。那这样理解的话,就是说明,如果是用百分比来作 background-position 的属性值的话,那么背景图片相对于容器的中心点是随时都在改变的。

按照我的理解,就是把sprite图上的某一个点移动到元素上的某一个点,让这两个点重合。举个例子:我有一个400*400的div,200*200的sprite图,然后我给元素设置了background-position: 100% 100%;那么我们先找出这两个点,元素的(100% 100%)点A就是元素的右下角,sprite图的(100% 100%)点B在sprite图的右下角。如图

Paste_Image.png

加了background-position: 100% 100%;之后,就是把sprite图的B点移动到A点。如图

400.jpg

,就是这么个情况。代码如下:

<style type="text/css">
    .box{
        width: 400px;
        height: 400px;
        border: 1px solid #ccc;
        margin: 100px auto;
        background: url(../images/200-1.jpg) no-repeat;
        background-position: 100% 100%;
    }
</style>
<div class="box">
    
</div>

但是这个是在我们知道百分比的情况下的,而我们需要做positon定位的时候需要的正是这个百分比,所以我们应该根据其他的变量把百分比求出来。我们可以把以上的情况转换成跟元素、sprite图、坐标有关的场景,比如类似:

当给元素加background-position: 10% 20%的时候,sprite图会先参考自身移动(-10% -20%)也就是改变自己的中心点,然后再根据元素的宽高来移动(10% 20%),最后sprite图会移动到一个点(X Y)。这个点就是我们需要显示在元素中icon的坐标。根据这个方式,总是可以把sprite图的某个点定位到元素的(0 0)位置。具体操作的demo

那重新梳理一下:
如果使用px来定位的话,那么sprite图是以左上角(0 0) 为中心点的,比如加上background-position:10px 20px;(此时sprite图的中心点在(0 0))那么图片的左边缘跟上边缘会往下移动20px,往右移动10px;我们平常使用的也是这种情况。

但是使用百分比来定位的话,那图片就不是以左上角为中心点了。比如加上background-position:10% 20%;那么背景sprite图片的中心点就会改变成图片 (10% 20%) 这个点了,比如原始sprite图片宽度为50px*50px,原始原始的中心点是(0 0);加了background-position:10% 20%;之后呢,中心点就变成了 (50px*10%  50px*20%) 也就是(5px, 10px)这个点,然后就会根据这个中心点来进行移动,假设元素的大小为200px*200px,根据推理,加了background-position:10% 20%;移动的步骤类似如下:

1、背景图片sprite图的中心点会改变成图片 10% 20% 这个点 即50*10% 50*20% 也就是(5px, 10px)(相当于把sprite图先移动(-5px -10px),也就是把sprite图的中心点移动端元素的左上角(0 0)处);

2、然后以sprite图的(5px 10px)点为中心点移动元素宽高度的10% 元素高度的20%,也就是往右往下移动了 20px 40px;
需要注意的是,这次的移动是以(5px 10px)这个中心点来移动的,就是把这个点先移动到父元素 0 0 的位置,再移动 20px 40px;
所以最终移动的距离是 (-5px+20px -10px+40px) 也就是 向右移动了15px 向下移动了30px 。

根据以上的推理,要想得到定位的百分比值(n m),我们需要 元素的宽高(w h), sprite图的宽高(k g),我们需要显示icon的坐标(x y),我们以向右向下移动端为正,向上向左为负。可以得到计算公式如下:

left: -n* k + n*w = -x
top: -m* g + m*h = -y

根据上面的公式,我们可以得到:

n = -x / (w-k) * 100%
m = -y / (h-g) * 100%

那举个例子:我们有一张200*200的sprite图,需要显示黄色的区块。


背景图200*200
  1. 当我们的div宽度为 100*100时,可以得出n:100%, m: 100%,所以我们应该给元素加上background-position: 100% 100%;代码及效果如下:
<style type="text/css">
    .box{
        width: 100px;
        height: 100px;
        border: 1px solid #ccc;
        margin: 100px auto;
        background: url(../images/200-1.jpg) no-repeat;
        background-position: 100% 100%;
    }
</style>
<div class="box">
</div>
100*100效果图
  1. 当div的宽度为 150*300时,可以得出n:200% m: -100%;所以我们应该给元素加上background-position: 200% -100%;效果如下
150*300效果图

经过验证,上面的计算公式的确是可以的,就是那么简单。但是我们也不能用一次就算一次吧,经过以上公式的整理,可以用sass写出一个fucntion 或者mixin,代码如下:

//$spriteWidth 雪碧图的宽度px
//$spriteHeight 雪碧图的高度px
//$iconWidth 需要显示icon的宽度px
//$iconHeight 需要显示icon的高度px
//$iconX icon的原始x坐标
//$iconY icon的原始y坐标
//
@mixin bgPosition($spriteWidth, $spriteHeight, $iconWidth, $iconHeight, $iconX, $iconY){

    background-position: (($iconX / ($spriteWidth - $iconWidth)) * 100% ($iconY / ($spriteHeight - $iconHeight)) * 100%); 
}

//使用的方式
.test-sprites{
    margin-top: 30px;

    ul{
        padding: 0;
        margin: 0;
    }

    li{
        width: 0.48rem;
        height: 0.7rem;
        overflow: hidden; 
        box-sizing: border-box; 
        margin-left: 0.3rem;
        float: left;
        background:transparent url('http://nos.netease.com/edu-image/9BC0742AEB1A0B756EFC71B9DF77E452.png') 0 -0.02rem no-repeat;
        background-size: 10.72rem 4.42rem;
    }

    .icon2{
        width: 0.74rem; 
        height: 0.64rem;
        @include bgPosition(1072, 442, 74, 64, 188, 5); 
    }

    .icon3{
        width: 0.71rem;
        height: 0.74rem;
        @include bgPosition(1072, 442, 71, 74, 391, 2);
    }

    .icon4{
        width: 0.72rem;
        height: 0.73rem;
        @include bgPosition(1072, 442, 72, 73, 591, 3); 
    }
    .icon5{
        width: 0.73rem;
        height: 0.73rem;
        @include bgPosition(1072, 442, 73, 73, 792, 1); 
    }
    .icon6{
        width: 0.67rem;
        height: 0.57rem;
        @include bgPosition(1072, 442, 67, 57, 996, 8);
    }
}

就是下面这个图片,按照这样的方式,经过实践是没有问题的


原始sprite图 1072*442

但是呢,以上的bgPosition感觉不够简洁,因为每次都要输入sprite图的宽高,那么可以在bgPosition的基础上再拓展一下;

//同一张sprite图,横图
@mixin bgPositionSameSprite($iconWidth, $iconHeight, $iconX, $iconY){

    $spriteWidth : 1072;
    $spriteHeight : 442;

    @include bgPosition($spriteWidth, $spriteHeight, $iconWidth, $iconHeight, $iconX, $iconY);
}

//同一张sprite图、竖图
@mixin bgPositionSameSprite-tow($iconWidth, $iconHeight, $iconX, $iconY){

    $spriteWidth : 300;
    $spriteHeight : 1000;

    @include bgPosition($spriteWidth, $spriteHeight, $iconWidth, $iconHeight, $iconX, $iconY);
}

这样我们就只要输入四个参数了,那还能不能再简洁点呢,那只能看情况了,如果你的每个icon大小都一样的话,是完全没有问题的,你只需要输入每个icon的坐标就行,比如这样的图

原始分享图片 220*220

然后你就可以写这样的一个方法

//同一张sprite图并且每个icon的大小相同
@mixin bgPositionSameSpriteAndWidth($iconX, $iconY){

    $spriteWidth : 220;
    $spriteHeight : 220;
    $iconWidth : 61;
    $iconHeight : 61;

    @include bgPosition($spriteWidth, $spriteHeight, $iconWidth, $iconHeight, $iconX, $iconY);
}
//使用
i{
    padding-top: 100%;
    width: 100%;
    display: block;
    background: url(http://nos.netease.com/edu-image/3A65D313376F13CE75CE01C2593BD1CE.png) 0 0 no-repeat;
    background-size: 2.2rem 2.2rem;
}

.i-sina{
    @include bgPositionSameSpriteAndWidth(10, 10);
}

.i-qzone{
    @include bgPositionSameSpriteAndWidth(80, 10);
}

.i-qq{
    @include bgPositionSameSpriteAndWidth(150, 10);
}

.i-douban{
    @include bgPositionSameSpriteAndWidth(10, 80);
}

.i-yixin{
    @include bgPositionSameSpriteAndWidth(80, 80);
}

.i-renren{
    @include bgPositionSameSpriteAndWidth(150, 80);
}

以上方案基本可以解决适配定位问题,还能解放生产力。由于浏览器对小数点的取舍而出现图片被截掉1px ,建议该图标的div稍微比图标大一点,或者加padding。如果有不对的地方,还望指正。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 转载请声明 原文链接地址 关注公众号获取更多资讯 第一部分 HTML 第一章 职业规划和前景 职业方向规划定位...
    程序员poetry阅读 16,510评论 32 459
  • 问答题47 /72 常见浏览器兼容性问题与解决方案? 参考答案 (1)浏览器兼容问题一:不同浏览器的标签默认的外补...
    _Yfling阅读 13,725评论 1 92
  • 选择qi:是表达式 标签选择器 类选择器 属性选择器 继承属性: color,font,text-align,li...
    wzhiq896阅读 1,730评论 0 2
  • 1、属性选择器:id选择器 # 通过id 来选择类名选择器 . 通过类名来选择属性选择器 ...
    Yuann阅读 1,615评论 0 7
  • 如果有来生,你最想做点啥? 如果有来生,我一定会在最初懂得臭美的年纪,缠着妈妈给我买几套合身又漂亮的衣服;...
    伊七七阅读 305评论 0 0