Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

「13」微信小程序之如何使用自定义组件封装原生 image 组件 #14

Open
BuptStEve opened this issue Jun 17, 2018 · 0 comments

Comments

@BuptStEve
Copy link
Owner

BuptStEve commented Jun 17, 2018

零、问题的由来

一般在前端展示图片时都会碰到这两个常见的需求:

  1. 图片未加载完成时先展示占位图,等到图片加载完毕后再展示实际的图片。
  2. 假如图片链接有问题(比如 404),依然展示占位图。甚至你还可以增加点击图片再次加载的功能。(例如知乎)

然鹅,小程序原生组件 image 并没有提供这些常用功能...

TuaImage

注:这里加了 2s 的延迟

一、常规操作

在小程序没还没推出自定义组件功能时,只能通过改变 Page 中的 data 来展示兜底的占位图,所以当时的处理方式十分蛋疼...

1.1.相同默认图

由于需要知道这个图片的数据路径,所以不得不在每个 image 上加上类似 data-img-path 的东西。

<view
    wx:for="{{ obj.arr }}"
    wx:key="imgSrc"
    wx:for-item="item"
    wx:for-index="itemIdx"
>
    <image
        src="{{ item.imgSrc }}"
        binderror="onImageError"
        data-img-path="obj.arr[{{ itemIdx }}].imgSrc"
    />
</view>
const DEFAULT_IMG = '/assets/your_default_img'

Page({
    data: {
        obj: {
            arr: [
                { imgSrc: 'your_img1' },
                { imgSrc: 'your_img2' },
            ],
        },
    },
    onImageError ({
        target: { dataset: { imgPath } },
    }) {
        this.setData({
            [imgPath]: DEFAULT_IMG,
        })
    },
})

1.2.不同默认图

如果默认图片不同呢?例如球员、球队和 feed 的默认图片一般都是不同的。

那么一般只好再增加一个属性例如 data-img-type 来标识默认图的类型。

<!-- 球队图 -->
<image
    ...
    data-img-type="team"
/>
<!-- 球员图 -->
<image
    ...
    data-img-type="player"
/>
const DEFAULT_IMG_MAP = {
    feed: '/assets/default_feed',
    team: '/assets/default_team',
    player: '/assets/default_player',
}

Page({
    data: {
        obj: {
            arr: [
                { imgSrc: 'your_img1' },
                { imgSrc: 'your_img2' },
            ],
        },
    },
    onImageError ({
        target: { dataset: { imgPath, imgType } },
    }) {
        this.setData({
            [imgPath]: DEFAULT_IMG_MAP[imgType],
        })
    },
})

1.3.图片在模板中

页面层级浅倒还好,如果跨模板了,那么模板就可能要用一个类似于 pathPrefix 的属性来传递模板数据的路径前缀。

<!--
    球员排行模板
    pathPrefix: String
    playerList: Array
    ...
-->
<template name="srPlayerRank">
    <view
        wx:for="{{ playerList }}"
        wx:key="imgSrc"
        wx:for-item="item"
        wx:for-index="itemIdx"
    >
        <image
            src="{{ item.imgSrc }}"
            binderror="onImageError"
            data-img-type="player"
            data-img-path="{{ pathPrefix }}.playerList[{{ itemIdx }}].imgSrc"
        />
    </view>
</template>

最后在失败回调里调用 setData({ [path]: DEFAULT_IMG }) 重新设置图片地址。

就问你蛋不蛋疼?这一坨 data-img-path="{{ pathPrefix }}.playerList[{{ itemIdx }}].imgSrc" 代码真让人无发可脱...

啥啥啥写的这是啥

二、自定义组件

有了自定义组件后,用领袖【窃·格瓦拉】的话来说的话就是:“感觉好 door 了~”

2.1.原生自定义组件

原生写法一般要写4个文件:.json/.wxml/.js/.wxss

  • TuaImage.json
{
    "component": true
}
  • TuaImage.wxml
<!-- 加载中的图片 -->
<image
    hidden="{{ !isLoading }}"
    src="{{ errSrc }}"
    style="width: {{ width }}; height: {{ height }}; {{ styleStr }}"
    mode="{{ imgMode }}"
/>


<!-- 实际加载的图片 -->
<image
    hidden="{{ isLoading }}"
    src="{{ imgSrc || src }}"
    mode="{{ imgMode }}"
    style="width: {{ width }}; height: {{ height }}; {{ styleStr }}"
    bindload="_onImageLoad"
    binderror="_onImageError"
    lazy-load="{{ true }}"
/>
  • TuaImage.js
const DEFAULT_IMG = '/assets/your_default_img'

Component({
    properties: {
        // 图片地址
        src: String,
        // 图片加载中,以及加载失败后的默认地址
        errSrc: {
            type: String,
            // 默认是球队图标
            value: DEFAULT_IMG,
        },
        width: {
            type: String,
            value: '48rpx',
        },
        height: {
            type: String,
            value: '48rpx',
        },
        // 样式字符串
        styleStr: {
            type: String,
            value: '',
        },
        // 图片裁剪、缩放的模式(详见文档)
        imgMode: {
            type: String,
            value: 'scaleToFill',
        },
    },
    data: {
        imgSrc: '',
        isLoading: true,
    },
    methods: {
        // 加载图片出错
        _onImageError (e) {
            this.setData({
                imgSrc: this.data.errSrc,
            })
            this.triggerEvent('onImageError', e)
        },
        // 加载图片完毕
        _onImageLoad (e) {
            this.setData({ isLoading: false })
            this.triggerEvent('onImageLoad', e)
        },
    },
})

布吉岛大家使用原生写法时有木有一些感觉不方便的地方:

  • 4个文件:.json/.wxml/.js/.wxss,这样老需要切来切去的降低效率
  • properties 是什么鬼?大家(React/Vue)一般不都用 props 么?
  • style="width: {{ width }}; height: {{ height }}; {{ styleStr }}" 样式字符串怎么辣么长...

2.2.TuaImage.vue

所以以下是一个使用单文件组件封装原生 image 组件的例子。

  • 使用单文件组件将配置、模板、脚本、样式写在一个文件中,方便维护。
  • 使用计算属性 computed 将样式字符串写在 js 中。
  • 使用 this.imgSrc = this.errSrc 而不是 this.setData 来改变 data
<config>
{
    "component": true
}
</config>

<template lang="wxml">
    <!-- 加载中的图片 -->
    <image
        hidden="{{ !isLoading }}"
        src="{{ errSrc }}"
        style="{{ imgStyleStr }}"
        mode="{{ imgMode }}"
    />

    <!-- 实际加载的图片 -->
    <image
        hidden="{{ isLoading }}"
        src="{{ imgSrc || src }}"
        mode="{{ imgMode }}"
        style="{{ imgStyleStr }}"
        bindload="_onImageLoad"
        binderror="_onImageError"
        lazy-load="{{ true }}"
    />
</template>

<script>
/**
 * 图片组件,能够传递备用图片以防图片失效
 * https://developers.weixin.qq.com/miniprogram/dev/component/image.html
 */

// 也可以设置为网络图片如: https://foo/bar.png
const DEFAULT_IMG = '/assets/your_default_img'

export default {
    props: {
        // 图片地址
        src: String,
        // 图片加载中,以及加载失败后的默认地址
        errSrc: {
            type: String,
            // 默认是球队图标
            default: DEFAULT_IMG,
        },
        width: {
            type: String,
            default: '48rpx',
        },
        height: {
            type: String,
            default: '48rpx',
        },
        // 样式字符串
        styleStr: {
            type: String,
            default: '',
        },
        // 图片裁剪、缩放的模式(详见文档)
        imgMode: {
            type: String,
            default: 'scaleToFill',
        },
    },
    data () {
        return {
            imgSrc: '',
            isLoading: true,
        }
    },
    computed: {
        // 样式字符串
        imgStyleStr () {
            return `width: ${this.width}; height: ${this.height}; ${this.styleStr}`
        },
    },
    methods: {
        // 加载图片出错
        _onImageError (e) {
            this.imgSrc = this.errSrc
            this.$emit('onImageError', e)
        },
        // 加载图片完毕
        _onImageLoad (e) {
            this.isLoading = false
            this.$emit('onImageLoad', e)
        },
    },
}
</script>

<style lang="scss">

</style>

采用框架是 tua-mp:

相关文章:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant