工作中遇到的小技巧1

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来实现图片的自适应变化,以此来解决图片的自适应是一种非常实用且非常有效的方式。如果后续有开发需要的情况,可以以此种封装方式进行图片的再封装。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容