uni-app中image组件的图片适配大小的方法
因工作需求,原生开发过小程序,接触过小程序的image组件,也开发过uni-app,也接触了uni-app的image组件,我发现这两个框架对于image组件的封装都涉及到一个属性,名叫mode,用来展示图片的各种自适应能力。
<template>
<div class='image'>
<image mode="scaleToFill" src="http://3gimg.qq.com/BabytingWeb/yunying/home/238b96dfc20a98924c1c979b801cfa32.png"></image>
<view>不保持纵横比缩放图片,使图片的宽高完全拉伸至填满 image 元素</view>
<image mode="aspectFit" src="http://3gimg.qq.com/BabytingWeb/yunying/home/238b96dfc20a98924c1c979b801cfa32.png"></image>
<view>保持纵横比缩放图片,使图片的长边能完全显示出来。也就是说,可以完整地将图片显示出来。</view>
<image mode="aspectFill" src="http://3gimg.qq.com/BabytingWeb/yunying/home/238b96dfc20a98924c1c979b801cfa32.png"></image>
<view>保持纵横比缩放图片,只保证图片的短边能完全显示出来。也就是说,图片通常只在水平或垂直方向是完整的,另一个方向将会发生截取。</view>
<image mode="widthFix" src="http://3gimg.qq.com/BabytingWeb/yunying/home/238b96dfc20a98924c1c979b801cfa32.png"></image>
<view>宽度不变,高度自动变化,保持原图宽高比不变</view>
<image mode="top" src="http://3gimg.qq.com/BabytingWeb/yunying/home/238b96dfc20a98924c1c979b801cfa32.png"></image>
<view>不缩放图片,只显示图片的顶部区域</view>
<image mode="bottom" src="http://3gimg.qq.com/BabytingWeb/yunying/home/238b96dfc20a98924c1c979b801cfa32.png"></image>
<view>不缩放图片,只显示图片的底部区域</view>
</div>
</template>
剖析image组件的封装原理
直接上源码
html
<template>
<uni-image v-on="$listeners">
<div
ref="content"
:style="modeStyle" />
<img :src="realImagePath">
<v-uni-resize-sensor
v-if="mode === 'widthFix'"
ref="sensor"
@resize="_resize" />
</uni-image>
</template>
js
export default {
name: 'Image',
props: {
src: {
type: String,
default: ''
},
mode: {
type: String,
default: 'scaleToFill'
},
// TODO 懒加载
lazyLoad: {
type: [Boolean, String],
default: false
}
},
data () {
return {
originalWidth: 0,
originalHeight: 0,
availHeight: '',
sizeFixed: false
}
},
computed: {
ratio () {
return this.originalWidth && this.originalHeight ? this.originalWidth / this.originalHeight : 0
},
realImagePath () {
return this.src && this.$getRealPath(this.src)
},
modeStyle () {
let size = 'auto'
let position = ''
let repeat = 'no-repeat'
switch (this.mode) {
case 'aspectFit':
size = 'contain'
position = 'center center'
break
case 'aspectFill':
size = 'cover'
position = 'center center'
break
case 'widthFix':
size = '100% 100%'
break
case 'top':
position = 'center top'
break
case 'bottom':
position = 'center bottom'
break
case 'center':
position = 'center center'
break
case 'left':
position = 'left center'
break
case 'right':
position = 'right center'
break
case 'top left':
position = 'left top'
break
case 'top right':
position = 'right top'
break
case 'bottom left':
position = 'left bottom'
break
case 'bottom right':
position = 'right bottom'
break
default:
size = '100% 100%'
position = '0% 0%'
break
}
return `background-position:${position};background-size:${size};background-repeat:${repeat};`
}
},
watch: {
src (newValue, oldValue) {
this._loadImage()
},
mode (newValue, oldValue) {
if (oldValue === 'widthFix') {
this.$el.style.height = this.availHeight
this.sizeFixed = false
}
if (newValue === 'widthFix' && this.ratio) {
this._fixSize()
}
}
},
mounted () {
this.availHeight = this.$el.style.height || ''
this._loadImage()
},
methods: {
_resize () {
if (this.mode === 'widthFix' && !this.sizeFixed) {
this._fixSize()
}
},
_fixSize () {
const elWidth = this._getWidth()
if (elWidth) {
let height = elWidth / this.ratio
// fix: 解决 Chrome 浏览器上某些情况下导致 1px 缝隙的问题
if (typeof navigator && navigator.vendor === 'Google Inc.' && height > 10) {
height = Math.round(height / 2) * 2
}
this.$el.style.height = height + 'px'
this.sizeFixed = true
}
},
_loadImage () {
this.$refs.content.style.backgroundImage = this.src ? `url(${this.realImagePath})` : 'none'
const _self = this
const img = new Image()
img.onload = function ($event) {
_self.originalWidth = this.width
_self.originalHeight = this.height
if (_self.mode === 'widthFix') {
_self._fixSize()
}
_self.$trigger('load', $event, {
width: this.width,
height: this.height
})
}
img.onerror = function ($event) {
_self.$trigger('error', $event, {
errMsg: `GET ${_self.src} 404 (Not Found)`
})
}
img.src = this.realImagePath
},
_getWidth () {
const computedStyle = window.getComputedStyle(this.$el)
const borderWidth = (parseFloat(computedStyle.borderLeftWidth, 10) || 0) + (parseFloat(computedStyle.borderRightWidth,
10) || 0)
const paddingWidth = (parseFloat(computedStyle.paddingLeft, 10) || 0) + (parseFloat(computedStyle.paddingRight, 10) || 0)
return this.$el.offsetWidth - borderWidth - paddingWidth
}
}
}
css
uni-image {
width: 320px;
height: 240px;
display: inline-block;
overflow: hidden;
position: relative;
}
uni-image[hidden] {
display: none;
}
uni-image>div {
width: 100%;
height: 100%;
}
uni-image>img {
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
}
uni-image>.uni-image-will-change {
will-change: transform;
}
通过源码我们了解到,uni-app的image组件是通过div的background来实现的自适应图片的。通过设置background-size和background-position来确定图片的尺寸以及显示位置。
background-image属性设置图片,如果设置当前元素大小为宽高100%的情况下,不设置background-size,可以实现图片的等大展示,也就是说图片多大,div就多大。如果设置为contain,则为父元素多大,div就多大,再如果设置为cover,那就是保持图像的纵横比并将图像缩放成将完全覆盖背景定位区域的最小大小。
通过background-image来实现图片的自适应变化,以此来解决图片的自适应是一种非常实用且非常有效的方式。如果后续有开发需要的情况,可以以此种封装方式进行图片的再封装。