background属性妙用

7/26/2020 CSS

经过一周的样式折腾,发现了 background 的新大陆。记录一下用法
文章一路下来比较枯燥,毕竟只是记录探索过程,很多东西还是要自己动手实践下!

background 详细的介绍和基础用法直接放上 MDN 的链接 background (opens new window)

# background 和 img 标签对比

img 标签常用于显示图片,但是会带来很多问题:

  1. 图片加载会阻塞后面的渲染,如果是一个商品/图片列表。加载会一直阻塞后面的代码执行
  2. img 标签很难满足我们所有的需求,比如图片的比例缩放的控制等,只能规定具体的大小,不太灵活
  3. 图片轻而易举的就会被人 右键另存为
  4. img 标签内不能在放入内容,如果想图片上叠加内容,还需要额外的定位代码等

虽然 img 标签的确存在一些小毛病,不过 img 标签存在即合理,比如 imgalt 属性,有利于 SEO,添加图片的曝光率等
加上现在很多需求都需要长按保存二维码,那 img 标签就在合适不过了。

  • 如果只是为了显示图片:推荐使用 background 来加载图片,因为 css 的解析会在 dom 解析后才开始加载图片,不会阻塞主内容渲染
  • 想实现各种图片的模式(比如小程序 image 标签的 mode 功能),使用 background 即可轻松实现
  • 如果需要在图片上覆盖内容,那 background 就是为了这个需求实现的呀

# background 实现的各种缩放模式

这里用到的 css 属性:

  • background-image:用于显示图片
  • background-size:用于控制图片显示的比例,显示的模式
  • background-position:用于控制执行模式下图片展示的部分
  • background-repeat:是否开启重复渲染,通常都是no-repeat

# 以 vue 为例,模仿小程序封装一个自己的 image 标签

image 标签要实现的功能:

参数 是否必填 默认值 额外说明
url '' 图片链接地址
ratio 1:1 图片缩放的比例,如果只输入一个数字(如:16)则会被转换为(16:16)
width 0 组件宽度,默认是 0,如果不输入宽度,则以高度为主,配合缩放比例计算宽度
height 0 组件高度,默认是 0,优先以宽度为主,配合缩放比例计算宽高
mode aspectFit 图片缩放模式,具体模式参考下面的表格

mode 的合法值

说明
scaleToFill 缩放模式,不保持纵横比缩放图片,使图片的宽高完全拉伸至填满组件
aspectFit 缩放模式,保持纵横比缩放图片,使图片的长边能完全显示出来
aspectFill 缩放模式,保持纵横比缩放图片,只保证图片的短边能完全显示出来
widthFix 缩放模式,宽度不变,高度自动变化,保持原图宽高比不变
heightFix 缩放模式,高度不变,宽度自动变化,保持原图宽高比不变
top 裁剪模式,不缩放图片,只显示图片的顶部区域
bottom 裁剪模式,不缩放图片,只显示图片的底部区域
center 裁剪模式,不缩放图片,只显示图片的中间区域
left 裁剪模式,不缩放图片,只显示图片的左边区域
right 裁剪模式,不缩放图片,只显示图片的右边区域
topLeft 裁剪模式,不缩放图片,只显示图片的左上边区域
topRight 裁剪模式,不缩放图片,只显示图片的右上边区域
bottomLeft 裁剪模式,不缩放图片,只显示图片的左下边区域
bottomRight 裁剪模式,不缩放图片,只显示图片的右下边区域
查看完整代码
<template>
  <div ref="imageTag">
    <div :class="['image-tag',mode]" :style="previewStyle">
      <slot></slot>
    </div>
  </div>
</template>

<style scoped>
  .image-tag {
    width: 200px;
    /* 默认模式,防止mode值不存在 */
    background-size: contain;
    background-repeat: no-repeat;
    background-position: center center;
  }
  /* 缩放模式,不保持纵横比缩放图片,使图片的宽高完全 “拉伸” 至填满 image 元素 */
  .scaleToFill {
    background-size: 100% 100%;
  }
  /* 缩放模式,保持纵横比缩放图片,使图片的 “长边” 能完全显示出来。 */
  .aspectFit {
    background-size: contain;
  }
  /* 缩放模式,保持纵横比缩放图片,只保证图片的 “短边” 能完全显示出来。 */
  .aspectFill {
    background-size: cover;
  }
  /* 缩放模式,宽度不变,“高度自动变化”,保持原图宽高比不变 */
  .widthFix {
    background-size: 100% auto;
  }
  /* 缩放模式,高度不变,“宽度自动变化”,保持原图宽高比不变 */
  .heightFix {
    background-size: auto 100%;
  }
  /* 裁剪模式,不缩放图片,只显示图片的顶部区域。往下的css都是和这个原理一致 */
  .top {
    background-size: cover;
    background-position: top;
  }
  .bottom {
    background-size: cover;
    background-position: bottom;
  }
  .center {
    background-size: cover;
    background-position: center;
  }
  .left {
    background-size: cover;
    background-position: left;
  }
  .right {
    background-size: cover;
    background-position: right;
  }
  .topLeft {
    background-size: cover;
    background-position: top left;
  }
  .topRight {
    background-size: cover;
    background-position: top right;
  }
  .bottomLeft {
    background-size: cover;
    background-position: bottom left;
  }
  .bottomRight {
    background-size: cover;
    background-position: bottom right;
  }
</style>

<script>
  export default {
    name: 'Image',
    data() {
      return {
        isMounted: false
      }
    },
    computed: {
      previewStyle() {
        let resStyle = {
          backgroundImage: `url('${this.url}')`
        }
        // 获取ref节点需要在mounted之后
        if (!this.isMounted) return resStyle

        let ratioArr = this.ratio.split(':')
        let widthRatio = parseInt(ratioArr[0] || '1')
        // 处理错误值
        widthRatio = isNaN(widthRatio) || widthRatio < 0 ? 1 : widthRatio

        // 高度比例不存在默认就拿宽度的,形成 1:1
        let heightRatio = ratioArr[1] ? parseInt(ratioArr[1]) : widthRatio
        heightRatio = isNaN(heightRatio) || heightRatio < 0 ? 1 : heightRatio

        if (this.width && this.width !== 0) {
          resStyle.width = this.width + 'px'
          resStyle.height = (this.width * heightRatio) / widthRatio + 'px'
        } else if (this.height && this.height !== 0) {
          resStyle.height = this.height + 'px'
          resStyle.width = (this.height * widthRatio) / heightRatio + 'px'
        } else {
          // 宽高都没传,手动获取节点宽度
          let width = this.$refs.imageTag.clientWidth
          resStyle.width = width + 'px'
          resStyle.height = (width * heightRatio) / widthRatio + 'px'
        }
        return resStyle
      }
    },
    mounted() {
      this.isMounted = true
    },
    methods: {},
    props: {
      // 宽高比例
      ratio: {
        type: String,
        default: '1:1'
      },
      // 组件宽度
      width: {
        type: Number,
        default: 0
      },
      // 组件高度(没有传宽度的时候,高度才生效,默认宽度为主)
      height: {
        type: Number,
        default: 0
      },
      // 图片预览的地址
      url: {
        type: String,
        default: ''
      },
      // 图片缩放模式
      mode: {
        type: String,
        default: 'aspectFit'
      }
    }
  }
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148

# background 使用多张背景图

比如要实现这么一个框框,大小不确定,不过上下的元素都是一致的,最好的就是把图片切成 2 部分,分别显示

话不多说,直接上 css
, 分隔开多张背景,下面的 positions 属性也是一样,使用 , 隔开,即可实现一个 div 内,控制每一张图片的显示位置

.bg {
  width: 100px;
  height: 100px;
  background-repeat: no-repeat;
  background-size: 100% auto;
  background-image: url('./head.png'), url('./footer.png');
  background-position: center top, center bottom;
}
1
2
3
4
5
6
7
8

# background 实现帧动画效果

有时候前端实现动画,并不需要 gif,可以使用一堆的图片,配合 background 来实现~

这里推荐一个库 gkajs/gka (opens new window)

使用特别简单,具体可以看 github 的文档咯

cnpm install gka # npm 安装是在是太慢了
gka <dir路径>
1
2

原理就是使用 @keyframes 和 css 的 animation 实现一直替换背景图,只要时间和帧数控制合适,即可实现 gif 效果
具体的效果,使用 gka 生成一次就知道啦

成品:

# 使用雪碧图优化帧动画效果

通过雪碧图,详细了解 background-position 属性

# 引出问题

上面说到了使用 background 实现 gif 动画,可是如果帧数太多,也会有问题,毕竟并发的请求只是 5 个/10 个,实现一个小动画动辄 20 张图以上,这样加载肯定是不行的咯,所以我们还是用到了 gka ,合并多图为雪碧图。具体合成可以看 gka的介绍。

但是gka 生成出来的 css 代码是固定好像素的,而我们实际使用的场景的宽高可能多数都是变化不定,那就要用到百分比

# 关于 background-position 使用 百分比的坑

我先贴上一张我演示的图(图片特别长 248400*200) 链接:图片链接 248400*200 (opens new window)

每一帧的宽度为 1300px。可是到了实际项目中,宽度是不固定的,于是我们需要换成百分比计算

初始代码长这样:我们要做的就是把 1300 换成对应的百分比

background-position 对于百分比的计算方式

比如: background-position:50% 50%;
并不是图片偏移 50% 。而且居中了!

得出来的像素才是图片偏移的位置 = (容器宽度 - 图片宽度)*50%px。

了解了 background-position 的计算方式后,看回我们的图片:

  • 一共 184 帧。图片总长度 248400
  • 一帧的宽度为 1300,高度为 200
  • 假设我们需要放在一个 500px 的容器中,那么图片的实际宽度应该为 18400% (因为 184 帧嘛)
  • 每次偏移为 500px

根据上面的推导,写出基础的代码为

@keyframe gif {
  0% {
    background-size: 18400% auto;
    background-image: url('../../../assets/screen/border-bg-imgs/footer-stpe2-sprites.png');
  }
  /* ... */
  100% {
    background-position: 100% 0px;
  }
}
1
2
3
4
5
6
7
8
9
10

那第帧需要偏移多少 px 呢?假设要偏移的百分比为x,显示图片的容器宽度假设为y,可以得出一套公式:

(容器宽度 - 图片宽度)*x = 偏移的像素(就是容器的宽度)
(y - 184*y)*x = y 其中 184 是我们上面的 184 帧
通过一番小学数学的计算得出:
x = 1/183 即:每一帧偏移的百分比为:1/(总帧数-1)
如果要算第二帧,第三帧,这是在百分比基础上继续 * 对应的帧数 所以我们案例中的图片每一帧。就可以很简单用 js 计算出来

for (let i = 1; i <= 183; i++) {
  console.log((i / 183) * 100) // 因为要算的是百分比,所以 * 100
}
1
2
3

轻而易举得出每一帧偏移的百分比,在对应套入 gka 生成的代码中,

即可实现上图 gif 的效果,并且现在已经不受限于固定的像素,而是根据容器宽度,自动偏移

Last Updated: 5/9/2021, 10:45:03 PM