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

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

推荐阅读更多精彩内容