第二章:微信小程序组件介绍及使用

560 阅读16分钟

第二章:微信小程序组件介绍及使用 (43讲)

07 | icon组件:关于图标的4个实现方案

组件属性的长度单位默认为px,2.4.0起支持传入单位(rpx/px)。

属性类型默认值必填说明最低版本
typestringicon的类型,有效值:success, success_no_circle, info, warn, waiting, cancel, download, search, clear1.0.0
sizenumber/string23icon的大小1.0.0
colorstringicon的颜色,同css的color1.0.0

实现方式

  1. 图片
  2. 背景图片(网络图片)
  3. css绘制
  4. 字体图标

08 | progress组件:如何自定义实现一个环形进度条?

09 | rich-text组件:如何单击预览rich-text中的图片并保存?

属性类型默认值必填说明最低版本
nodesarray/string[]节点列表/HTML String1.4.0
spacestring显示连续空格2.4.1

10 | view容器组件及Flex布局(一):学习容器组件view及其重要属性

属性类型默认值必填说明最低版本
hover-classstringnone指定按下去的样式类。当 hover-class="none" 时,没有点击态效果1.0.0
hover-stop-propagationbooleanfalse指定是否阻止本节点的祖先节点出现点击态1.5.0
hover-start-timenumber50按住后多久出现点击态,单位毫秒1.0.0
hover-stay-timenumber400手指松开后点击态保留时间,单位毫秒1.0.0

11 | view容器组件及Flex布局(二):介绍flex布局中常用的样式及样式值

一、Flex 布局是什么?

Flex 是 Flexible Box 的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性。

任何一个容器都可以指定为 Flex 布局。

.box{
  display: flex;
}

行内元素也可以使用 Flex 布局。

.box{
  display: inline-flex;
}

Webkit 内核的浏览器,必须加上-webkit前缀。

.box{
  display: -webkit-flex; /* Safari */
  display: flex;
}

注意,设为 Flex 布局以后,子元素的floatclearvertical-align属性将失效。

二、基本概念

采用 Flex 布局的元素,称为 Flex 容器(flex container),简称"容器"。它的所有子元素自动成为容器成员,称为 Flex 项目(flex item),简称"项目"。

img

容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis)。主轴的开始位置(与边框的交叉点)叫做main start,结束位置叫做main end;交叉轴的开始位置叫做cross start,结束位置叫做cross end

项目默认沿主轴排列。单个项目占据的主轴空间叫做main size,占据的交叉轴空间叫做cross size

三、容器的属性

以下6个属性设置在容器上。

  • flex-direction
  • flex-wrap
  • flex-flow
  • justify-content
  • align-items
  • align-content

3.1 flex-direction属性

flex-direction属性决定主轴的方向(即项目的排列方向)。

.box {
  flex-direction: row | row-reverse | column | column-reverse;
}

img

它可能有4个值。

  • row(默认值):主轴为水平方向,起点在左端。
  • row-reverse:主轴为水平方向,起点在右端。
  • column:主轴为垂直方向,起点在上沿。
  • column-reverse:主轴为垂直方向,起点在下沿。

3.2 flex-wrap属性

默认情况下,项目都排在一条线(又称"轴线")上。flex-wrap属性定义,如果一条轴线排不下,如何换行。

img

.box{
  flex-wrap: nowrap | wrap | wrap-reverse;
}

它可能取三个值。

(1)nowrap(默认):不换行。

img

(2)wrap:换行,第一行在上方。

img

(3)wrap-reverse:换行,第一行在下方。

img

3.3 flex-flow

flex-flow属性是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap

.box {
  flex-flow: <flex-direction> || <flex-wrap>;
}

3.4 justify-content属性

justify-content属性定义了项目在主轴上的对齐方式。

.box {
  justify-content: flex-start | flex-end | center | space-between | space-around;
}

img

它可能取5个值,具体对齐方式与轴的方向有关。下面假设主轴为从左到右。

  • flex-start(默认值):左对齐
  • flex-end:右对齐
  • center: 居中
  • space-between:两端对齐,项目之间的间隔都相等。
  • space-around:每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍。

3.5 align-items属性

align-items属性定义项目在交叉轴上如何对齐。

.box {
  align-items: flex-start | flex-end | center | baseline | stretch;
}

img

它可能取5个值。具体的对齐方式与交叉轴的方向有关,下面假设交叉轴从上到下。

  • flex-start:交叉轴的起点对齐。
  • flex-end:交叉轴的终点对齐。
  • center:交叉轴的中点对齐。
  • baseline: 项目的第一行文字的基线对齐。
  • stretch(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。

3.6 align-content属性

align-content属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。

.box {
  align-content: flex-start | flex-end | center | space-between | space-around | stretch;
}

img

该属性可能取6个值。

  • flex-start:与交叉轴的起点对齐。
  • flex-end:与交叉轴的终点对齐。
  • center:与交叉轴的中点对齐。
  • space-between:与交叉轴两端对齐,轴线之间的间隔平均分布。
  • space-around:每根轴线两侧的间隔都相等。所以,轴线之间的间隔比轴线与边框的间隔大一倍。
  • stretch(默认值):轴线占满整个交叉轴。

四、项目的属性

以下6个属性设置在项目上。

  • order
  • flex-grow
  • flex-shrink
  • flex-basis
  • flex
  • align-self

4.1 order属性

order属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。

.item {
  order: <integer>;
}

img

4.2 flex-grow属性

flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。

.item {
  flex-grow: <number>; /* default 0 */
}

img

如果所有项目的flex-grow属性都为1,则它们将等分剩余空间(如果有的话)。如果一个项目的flex-grow属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。

4.3 flex-shrink属性

flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。

.item {
  flex-shrink: <number>; /* default 1 */
}

img

如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,前者不缩小。

负值对该属性无效。

4.4 flex-basis属性

flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。

.item {
  flex-basis: <length> | auto; /* default auto */
}

它可以设为跟widthheight属性一样的值(比如350px),则项目将占据固定空间。

4.5 flex属性

flex属性是flex-grow, flex-shrinkflex-basis的简写,默认值为0 1 auto。后两个属性可选。

.item {
  flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
}

该属性有两个快捷值:auto (1 1 auto) 和 none (0 0 auto)。

建议优先使用这个属性,而不是单独写三个分离的属性,因为浏览器会推算相关值。

4.6 align-self属性

align-self属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch

.item {
  align-self: auto | flex-start | flex-end | center | baseline | stretch;
}

img

该属性可能取6个值,除了auto,其他都与align-items属性完全一致。

12 | 可移动容器及可移动区域(一):学习使用movable-view与movable-area组件

13 | 可移动容器及可移动区域(二):如何实现侧滑删除功能

14 | scroll-view介绍:在小程序中如何实现滚动锚定?

属性类型默认值必填说明最低版本
scroll-xbooleanfalse允许横向滚动1.0.0
scroll-ybooleanfalse允许纵向滚动1.0.0
upper-thresholdnumber/string50距顶部/左边多远时,触发 scrolltoupper 事件1.0.0
lower-thresholdnumber/string50距底部/右边多远时,触发 scrolltolower 事件1.0.0
scroll-topnumber/string设置竖向滚动条位置1.0.0
scroll-leftnumber/string设置横向滚动条位置1.0.0
scroll-into-viewstring值应为某子元素id(id不能以数字开头)。设置哪个方向可滚动,则在哪个方向滚动到该元素1.0.0
scroll-with-animationbooleanfalse在设置滚动条位置时使用动画过渡1.0.0
enable-back-to-topbooleanfalseiOS点击顶部状态栏、安卓双击标题栏时,滚动条返回顶部,只支持竖向1.0.0
enable-flexbooleanfalse启用 flexbox 布局。开启后,当前节点声明了 display: flex 就会成为 flex container,并作用于其孩子节点。2.7.3
scroll-anchoringbooleanfalse开启 scroll anchoring 特性,即控制滚动位置不随内容变化而抖动,仅在 iOS 下生效,安卓下可参考 CSS overflow-anchor 属性。2.8.2
refresher-enabledbooleanfalse开启自定义下拉刷新2.10.1
refresher-thresholdnumber45设置自定义下拉刷新阈值2.10.1
refresher-default-stylestring"black"设置自定义下拉刷新默认样式,支持设置 `blackwhitenone`, none 表示不使用默认样式2.10.1
refresher-backgroundstring"#FFF"设置自定义下拉刷新区域背景颜色2.10.1
refresher-triggeredbooleanfalse设置当前下拉刷新状态,true 表示下拉刷新已经被触发,false 表示下拉刷新未被触发2.10.1
enhancedbooleanfalse启用 scroll-view 增强特性2.12.0
bouncesbooleantrueiOS 下 scroll-view 边界弹性控制 (同时开启 enhanced 属性后生效)2.12.0
show-scrollbarbooleantrue滚动条显隐控制 (同时开启 enhanced 属性后生效)2.12.0
paging-enabledbooleanfalse分页滑动效果 (同时开启 enhanced 属性后生效)2.12.0
fast-decelerationbooleanfalse滑动减速速率控制 (同时开启 enhanced 属性后生效)2.12.0
binddragstarteventhandle滑动开始事件 (同时开启 enhanced 属性后生效) detail { scrollTop, scrollLeft }2.12.0
binddraggingeventhandle滑动事件 (同时开启 enhanced 属性后生效) detail { scrollTop, scrollLeft }2.12.0
binddragendeventhandle滑动结束事件 (同时开启 enhanced 属性后生效) detail { scrollTop, scrollLeft, velocity }2.12.0
bindscrolltouppereventhandle滚动到顶部/左边时触发1.0.0
bindscrolltolowereventhandle滚动到底部/右边时触发1.0.0
bindscrolleventhandle滚动时触发,event.detail = {scrollLeft, scrollTop, scrollHeight, scrollWidth, deltaX, deltaY}1.0.0
bindrefresherpullingeventhandle自定义下拉刷新控件被下拉2.10.1
bindrefresherrefresheventhandle自定义下拉刷新被触发2.10.1
bindrefresherrestoreeventhandle自定义下拉刷新被复位2.10.1
bindrefresheraborteventhandle自定义下拉刷新被中止2.10.1

15 | scroll-view介绍:如果渲染一个滚动的长列表?

<wxs module="refresh">
	var pullingMessage = "下拉刷新"

	module.exports = {
		onRefresh: function(e, instance) {
			// 此时手拉开了,进入了加载中的状态
			pullingMessage = "更新中"
			console.log(pullingMessage)
			instance.callMethod("setData", {
				pullingMessage: pullingMessage,
				refresherTriggered: true
			})
			instance.callMethod("willCompleteRefresh", {})
		},
		onAbort: function(e, instance) {
			// 异常状态,例如被事件突然打断,事件包括电话等,被迫松手了
			pullingMessage = "下拉刷新"
			console.log(pullingMessage)
		},
		onRestore: function(e, instance) {
			// 回去了,松手了,恢复原位了,不刷了
			pullingMessage = "下拉刷新"
			console.log(pullingMessage)
		},
		onPulling: function(e, instance) {
			// 80的高度,因为refresher-threshold设置的是80,手指按住往下拉的状态
			var p = Math.min(e.detail.dy / 80, 1)
			// console.log(p)
			// 这里在视图层,不怕频繁操作DOM
			var icon = instance.selectComponent('#refresherIcon')
			icon.setStyle({
				opacity: p,
				transform: "rotate(" + (90 + p * 180) + "deg)"
			})
			var view = instance.selectComponent('.refresh-container')
			view.setStyle({
				opacity: p,
				transform: "scale(" + p + ")"
			})
			if (e.detail.dy >= 80) {
				if (pullingMessage == "下拉刷新") {
					pullingMessage = "释放更新"
					instance.callMethod("setData", {
						pullingMessage
					})
				}
			}
		}
	}
</wxs>
<scroll-view scroll-y 
             style="width: 100%; height: 400px;overflow-anchor:auto;" 
             bindscroll="onScroll" 
             bindscrolltoupper="onScrolltoupper" 
             scroll-top="{{scrollTopValue}}" 
             scroll-into-view="{{scrollIntoViewId}}" 
             scroll-with-animation 
             enable-back-to-top 
             enable-flex 
             scroll-anchoring 
             refresher-enabled 
             refresher-threshold="{{80}}" 
             refresher-default-style="none" 
             refresher-background="#FFF"
             bindrefresherpulling="{{refresh.onPulling}}"
             bindrefresherrefresh="{{refresh.onRefresh}}" 
             bindrefresherrestore="{{refresh.onRestore}}"
             bindrefresherabort="{{refresh.onAbort}}" 
             refresher-triggered="{{refresherTriggered}}"
             >
		<view slot="refresher" class="refresh-container" style="display: block; width: 100%; height: 80px; background: #F8f8f8; display: flex; align-items: center;">
			<view class="view1" style="position: absolute; text-align: center; width: 100%;display:flex;align-items:center;justify-content:center;color:#888;">
				<mp-icon id="refresherIcon" icon="arrow" color="#888" size="{{20}}" style="margin-right:5px;transform:rotate(90deg)"></mp-icon>
				<text style="min-width:80px;text-align:left;">{{pullingMessage}}</text>
			</view>
		</view>

		<view wx:for="{{arr}}" id="view{{item+1}}" style="display: flex;height: 100px;">
			<text style="position:relative;top:5px;left:5px;color:black;">{{item+1}}</text>
			<image src="https://cdn.nlark.com/yuque/0/2020/jpeg/1252071/1586359160786-8d5b7738-3ad3-43e7-bf0a-738c58365645.jpeg"></image>
			<image src="https://cdn.nlark.com/yuque/0/2020/jpeg/1252071/1586359160786-8d5b7738-3ad3-43e7-bf0a-738c58365645.jpeg"></image>
			<image src="https://cdn.nlark.com/yuque/0/2020/jpeg/1252071/1586359160786-8d5b7738-3ad3-43e7-bf0a-738c58365645.jpeg"></image>
			<image src="https://cdn.nlark.com/yuque/0/2020/jpeg/1252071/1586359160786-8d5b7738-3ad3-43e7-bf0a-738c58365645.jpeg"></image>
			<image src="https://cdn.nlark.com/yuque/0/2020/jpeg/1252071/1586359160786-8d5b7738-3ad3-43e7-bf0a-738c58365645.jpeg"></image>
		</view>
	</scroll-view>
const app = getApp()
let viewId = 5

Page({
  data: {
    arr: [],
    triggered: false,
    scrollTopValue: 0,
    scrollIntoViewId: '',
    pullingMessage: '下拉刷新', //下拉刷新,释放更新,加新中...
    refresherTriggered: false, //
    tabs: []
  },
  willCompleteRefresh() {
    console.log('更新中')
    let intervalId = setInterval(() => {
      let pullingMessage = this.data.pullingMessage
      console.log(pullingMessage, pullingMessage == '更新中')
      if (pullingMessage.length < 7) {
        pullingMessage += '.'
      } else {
        pullingMessage = '更新中'
      }
      this.setData({
        pullingMessage
      })
    }, 500)
    setTimeout(() => {
      console.log('更新完成了')
      clearInterval(intervalId)
      this.setData({
        pullingMessage: "已刷新",
        refresherTriggered: false,
      })
    }, 2000)
  },
  unshiftOnePic() {
    let arr = this.data.arr
    arr.unshift(arr.length + 1)
    this.setData({
      arr
    })
  },
  scrollToView1() {
    viewId += 2
    this.setData({
      scrollIntoViewId: 'childview' + viewId
    })
    console.log(this.data.scrollIntoViewId)
  },

  onReady: function () {
    var ctx = createRecycleContext({
      id: 'recycleId',
      dataKey: 'recycleList',
      page: this,
      itemSize: {
        width: rpx2px(650),
        height: rpx2px(100)
      }
    })
    let newList = []
    for (let i = 0; i < 20; i++) {
      newList.push({
        id: i,
        name: `标题${i + 1}`
      })
    }
    ctx.append(newList)

    // 
    const arr = []
    for (let i = 0; i < 20; i++) arr.push(i)
    this.setData({
      arr
    })

    setTimeout(() => {
      this.setData({
        triggered: true,
      })
    }, 1000)
    // 
    let activeTab = 0,
      page = 1,
      res = {
        something: ''
      }
    let tabsData = this.data.tabs[activeTab] || {
      list: []
    }
    tabsData.page = page + 1
    tabsData.list.push(res)
    let key = `tabs[${activeTab}]`
    this.setData({
      [key]: tabsData
    })
    console.log(this.data.tabs)
  },

  onPulling(e) {
    console.log('onPulling:', e)
  },

  onRefresh() {
    if (this._freshing) return
    this._freshing = true
    setTimeout(() => {
      this.setData({
        triggered: false,
      })
      this._freshing = false
    }, 3000)
  },

  onRestore(e) {
    console.log('onRestore:', e)
  },

  onAbort(e) {
    console.log('onAbort', e)
  },
  onScroll(e) {
    console.log(e.detail.scrollTop, e.detail.scrollLeft, e.detail.scrollHeight, e.detail.scrollWidth)
  },
  onScrolltoupper(e) {
    console.log('已达顶部后,小于50,是一种状态')
  },
  plusScrollUpValue() {
    this.setData({
      scrollTopValue: this.data.scrollTopValue + 50
    })
  },
  viewScrollToUpperEvent(e) {
    console.log('测试scrolltoupper事件', e.detail);
  }
})

16 | 滚动选择器(一):学习使用选择器组件

Component({
    behaviors: ['wx://form-field'],
    options: {
        addGlobalClass: true,
    },
    properties: {
        extClass: {type: String, value: ''},
        name: {type: String, value: ''},
        value: {type: String, value: ''},
        options: {type: Array, value: []},
        labelField: {type: String, value: 'label'},
        valueField: {type: String, value: 'value'},
    },
    data: {
        optionsArray: [],
        currentIndex: 0,
    },
    observers: {
        'value,options': function () {
            this.getCurrentIndex();
            this.getOptionsArray();
        },
    },
    methods: {
        bindChange: function (e) {
            const index = e.detail.value;
            if (index > -1) {
                const value = this.data.options[index][this.data.valueField];
                this.setData({
                    currentIndex: index,
                    value: value
                });
                let eventDetail = {
                    value: value
                }
                this.triggerEvent('change', eventDetail, {})
            }
        },
        getOptionsArray() {
            let array = this.data.options.map(v => {
                return v[this.data.labelField];
            });
            this.setData({optionsArray: array})
        },
        getCurrentIndex() {
            let index = this.data.options.findIndex(v => {
                return v[this.data.valueField] === this.data.value;
            })
            this.setData({currentIndex: index})
        }
    }
})

<picker bindchange="bindChange"
        value="{{currentIndex}}"
        range="{{optionsArray}}"
>
    <input type="text"
           class="weui-input {{extClass}}"
           value="{{optionsArray[currentIndex]}}"
           placeholder="请选择"
           disabled="{{true}}"
    />
    <input type="text"
           class="hidden"
           name="{{name}}"
           model:value="{{value}}"
    />
</picker>

17 | 滚动选择器(二):使用两种方式自定义实现省、市、区三级联动的选择器

以下是通过 picker 实现

const {area} = require('../../components/picker-area/area');
const getAreaById = (id) => {
    return area[id] || []
}
Component({
    options: {
        addGlobalClass: true,
    },
    properties: {
        extClass: {type: String, value: ''},
        prov: {type: String, value: ''},
        city: {type: String, value: ''},
        dist: {type: String, value: ''},
    },
    data: {
        multiArray: [],
        objectMultiArray: [],
        multiIndex: [0, 0, 0],
        multiText: [],
    },
    lifetimes: {
        attached: function () {
            const provArray = getAreaById("prov");
            const cityArray = getAreaById(provArray[0].value);
            const distArray = getAreaById(cityArray[0].value);
            this.getMultiArray(provArray, cityArray, distArray);
        }
    },
    observers: {
        'prov': function (prov) {
            if (prov) {
                const {prov, city, dist} = this.data;
                const provArray = getAreaById("prov");
                const cityArray = getAreaById(prov);
                const distArray = getAreaById(city);
                let multiIndex = [];
                let multiText = [];
                provArray.forEach((v, i) => {
                    if (v.value === prov) {
                        multiIndex.push(i);
                        multiText.push(v.label);
                    }
                })
                cityArray.forEach((v, i) => {
                    if (v.value === city) {
                        multiIndex.push(i);
                        multiText.push(v.label);
                    }
                })
                distArray.forEach((v, i) => {
                    if (v.value === dist) {
                        multiIndex.push(i);
                        multiText.push(v.label);
                    }
                })
                this.getMultiArray(provArray, cityArray, distArray);
                this.setData({
                    multiIndex,
                    multiText
                });
            } else {
                this.setData({
                    multiText: []
                })
            }
        },
    },
    methods: {
        getMultiArray(provArray, cityArray, distArray) {
            const multiArray = [
                provArray.map(v => v.label),
                cityArray.map(v => v.label),
                distArray.map(v => v.label),
            ];
            const objectMultiArray = [provArray, cityArray, distArray];
            this.setData({
                multiArray,
                objectMultiArray
            });
        },
        bindMultiPickerChange: function (e) {
            const multiIndex = e.detail.value;
            const {multiArray, objectMultiArray} = this.data;
            let multiText = [];
            let multiValue = [];
            for (let i = 0; i < 3; i++) {
                if (multiArray[i][multiIndex[i]]) {
                    multiText.push(multiArray[i][multiIndex[i]]);
                }
                if (objectMultiArray[i][multiIndex[i]]) {
                    multiValue.push(objectMultiArray[i][multiIndex[i]]);
                }
            }
            this.setData({
                multiIndex,
                multiText
            });
            let eventDetail = {
                value: multiValue
            };
            this.triggerEvent('change', eventDetail, {})
        },
        bindMultiPickerColumnChange: function (e) {
            const {multiIndex, objectMultiArray} = this.data;
            let provArray = objectMultiArray[0];
            let cityArray = objectMultiArray[1];
            let distArray = objectMultiArray[2];
            let column = e.detail.column;
            let index = e.detail.value;
            if (column === 0) {
                cityArray = getAreaById(provArray[index].value);
                distArray = getAreaById(cityArray[0].value);
                this.setData({
                    multiIndex: [index, 0, 0]
                })
            } else if (column === 1) {
                distArray = getAreaById(cityArray[index].value);
                this.setData({
                    multiIndex: [multiIndex[0], index, 0]
                })
            }
            this.getMultiArray(provArray, cityArray, distArray);
        }
    }
})

<picker mode="multiSelector"
        bindchange="bindMultiPickerChange"
        bindcolumnchange="bindMultiPickerColumnChange"
        value="{{multiIndex}}"
        range="{{multiArray}}"
>
    <input type="text"
           class="weui-input {{extClass}}"
           value="{{multiText}}"
           placeholder="请选择"
           disabled="{{true}}"
    />
</picker>

module.exports.area = {
    "110000": [
        {"label": "市辖区", "value": "110100"},
        {"label": "县", "value": "110200"}
    ],
    "110100": [
        {"label": "东城区", "value": "110101"},
        {"label": "西城区", "value": "110102"},
        {"label": "崇文区", "value": "110103"},
        {"label": "宣武区", "value": "110104"},
        {"label": "朝阳区", "value": "110105"},
        {"label": "丰台区", "value": "110106"},
        {"label": "石景山区", "value": "110107"},
        {"label": "海淀区", "value": "110108"},
        {"label": "门头沟区", "value": "110109"},
        {"label": "房山区", "value": "110111"},
        {"label": "通州区", "value": "110112"},
        {"label": "顺义区", "value": "110113"},
        {"label": "昌平区", "value": "110114"},
        {"label": "大兴区", "value": "110115"},
        {"label": "怀柔区", "value": "110116"},
        {"label": "平谷区", "value": "110117"}
    ],
    ......
}

18 | 滑动选择器表单组件:如何基于wxs自定义一个竖向的slider?

19 | 页面链接组件:如何自定义一个导航栏?

{
  "usingComponents": {
    "navigation-bar":"/components/navigation-bar/index"
  },
  "navigationStyle": "custom"
}
// 该示例来源于https://developers.weixin.qq.com/community/develop/article/doc/00048e5ed784b037b959757385b413
// 有少量修改

Page({
  data: {
    loading: false,
    active: true
  },
  //点击back事件处理
  goBack: function () {
    wx.navigateBack();
    this.triggerEvent('back');
  },
  //返回首页
  goHome: function () {
    wx.reLaunch({
      url: '/pages/index/index'
    })
  },
  onPageScroll(res) {
    console.log(res);
    if (res.scrollTop > 400) {
      if (!this.data.active) {
        this.setData({
          active: true
        })
      }
    } else {
      if (this.data.active) {
        this.setData({
          active: false
        })
      }
    }
  }
})
<navigation-bar 
  ext-class="page-navigator-bar"
  active="{{active}}"
  loading="{{loading}}">
    <view class="left" slot="left">
      <icon bindtap="goBack" class="iconfont icon-back"></icon>
      <icon bindtap="goHome" class="iconfont icon-home"></icon>
    </view>
    <view slot="center">
      <view>自定义导航标题</view>
    </view>
</navigation-bar>

20 | image媒体组件(上):如何实现图片的懒加载?

  • webp 格式转换工具

  • mina-lazy-image

    小程序图片懒加载自定义组件

    背景

    小程序原生图片组件 image 提供的图片懒加载功能 lazy-load 限制过多,只针对 page 与 scroll-view 下的 image 有效。

    实现思路

    使用 wx.createIntersectionObserver() 判断图片是否出现在视口中并进行加载

    使用方法

    1. 安装组件
    npm install --save mina-lazy-image
    
    1. 在页面的 json 配置文件中添加 mina-lazy-image

    使用此组件需要依赖小程序基础库 2.2.2 版本,同时依赖开发者工具的 npm 构建。具体详情可查阅官方 npm 文档

    {
      "usingComponents": {
        "mina-lazy-image": "mina-lazy-image/index"
      }
    }
    
    1. WXML 文件中引用 mina-lazy-image

      <mina-lazy-image src="{{src}}" mode="widthFIx" image-class="custom-class-name"/>
      

      mina-lazy-image 的属性介绍如下:

      字段名类型必填描述
      srcString图片链接
      placeholderString占位图片链接
      modeString请参考 image 组件 mode 属性
      webpNumber请参考 image 组件 webp 属性
      showMenuByLongpressBoolean请参考 image 组件 show-menu-by-longpress 属性
      stylesString设置图片样式
      viewportObject默认为 {bottom: 0},配置图片显示区域

      mina-lazy-image 外部样式类

    image-class`, `image-container-class
    

21 | image媒体组件(下):开发中经常遇到的问题?

22 | 如何实现直播间功能?(一):了解live-pusher、live-player组件的主要属性及使用限制

23 | 如何实现直播间功能?(二):如何开启、使用腾讯云的云直播功能

24 | 如何实现直播间功能?(三):安装与使用ffmepg,及使用ffmpeg进行推拉流验证

25 | 如何实现直播间功能?(四):使用live-pusher、live-player组件在小程序中实现直播功能

26 | 如何实现直播间功能?(五):同层渲染

27 | 如何实现直播间功能?(六):live-pusher、live-player组件在开发中的常见问题

28 | web-view(一):了解session、cookie等相关基本概念

web-view

基础库 1.6.4 开始支持,低版本需做兼容处理

承载网页的容器。会自动铺满整个小程序页面,个人类型的小程序暂不支持使用。

客户端 6.7.2 版本开始,navigationStyle: customweb-view 组件无效

小程序插件中不能使用。

属性类型默认值必填说明最低版本
srcstringwebview 指向网页的链接。可打开关联的公众号的文章,其它网页需登录小程序管理后台配置业务域名。1.6.4
bindmessageeventhandler网页向小程序 postMessage 时,会在特定时机(小程序后退、组件销毁、分享)触发并收到消息。e.detail = { data },data是多次 postMessage 的参数组成的数组1.6.4
bindloadeventhandler网页加载成功时候触发此事件。e.detail = { src }1.6.4
binderroreventhandler网页加载失败的时候触发此事件。e.detail = { src }1.6.4

相关接口 1

web-view网页中可使用JSSDK 1.3.2提供的接口返回小程序页面。 支持的接口有:

接口名说明最低版本
wx.miniProgram.navigateTo参数与小程序接口一致1.6.4
wx.miniProgram.navigateBack参数与小程序接口一致1.6.4
wx.miniProgram.switchTab参数与小程序接口一致1.6.5
wx.miniProgram.reLaunch参数与小程序接口一致1.6.5
wx.miniProgram.redirectTo参数与小程序接口一致1.6.5
wx.miniProgram.postMessage向小程序发送消息,会在特定时机(小程序后退、组件销毁、分享)触发组件的message事件1.7.1
wx.miniProgram.getEnv获取当前环境1.7.1

示例代码

在开发者工具中预览效果

// <script type="text/javascript" src="https://res.wx.qq.com/open/js/jweixin-1.3.2.js"></script>

// javascript
wx.miniProgram.navigateTo({url: '/path/to/page'})
wx.miniProgram.postMessage({ data: 'foo' })
wx.miniProgram.postMessage({ data: {foo: 'bar'} })
wx.miniProgram.getEnv(function(res) { console.log(res.miniprogram) })

29 | web-view(二):了解常见的四种鉴权方式

  1. Http Basic Authentication
  2. Session-Cookie
  3. Token
  4. OAuth

30 | web-view(三):如何使用koa框架,及如何进行热加载?

31 | web-view(四):如何在服务器端实现cookie与sesson的生成?

32 | web-view(五):如何将session存储到服务器端,及如何实现token验证?

33 | web-view(六):基于koa中间件,实现微信一键登陆的后端接口

34 | web-view(七):实现微信用户一键登陆

35 | web-view(八):了解正确的微信登陆姿势

36 | web-view(九):web-view组件在开发的常见问题讲解

37 | WebGL介绍(一):了解WebGL相关的基础概念

38 | WebGL介绍(二):如何在小程序中取到WebGL上下文环境对象

39 | WebGL介绍(三):了解WebGL的世界坐标系

40 | WebGL介绍(四):重新认识右手坐标系及如何编写顶点着色器代码

41 | WebGL介绍(五):学习片断着色器编写,了解变量修饰变型uniform与attribute

42 | WebGL介绍(六):了解在WebGL中裁剪空间是如何裁剪出来的

43 | WebGL介绍(七):了解着色器变量值的绑定及三种三角形绘制模式之间的差异

44 | WebGL介绍(八):在着色器中使用共享变量,绘制一个颜色渐变的正方形

45 | WebGL介绍(九):完成动画

46 | WebGL介绍(十):绘制一个旋转的立方体

47 | WebGL介绍(十一):在3D绘制中使用纹理材质

48 | WebGL介绍(十二):如何创建相机、场景及光源

49 | WebGL介绍(十三):创建加载器、渲染器与控制器,完成3D模型文件的加载与展示