实战篇内容参考:
1、Lin Ui开源组件源码分析。https://doc.mini.talelin.com/
2、开发过程遇到问题。
操作dom元素需要JavaScript基础,因此学习之。
先介绍一些微信小程序的API函数:
参考文档:https://developers.weixin.qq.com/miniprogram/dev/api/wxml/SelectorQuery.in.html
(1)wx.createSelectorQuery()
(2)wx.createSelectorQuery().in(this) // 存在自定义组件使用该创建搜索器函数
(3)SelectorQuery.select(string selector)
(4)NodesRef.boundingClientRect(function callback)
(5)SelectorQuery.exec(function callback)
(1)返回一个 SelectorQuery
对象实例。在自定义组件或包含自定义组件的页面中,应使用 this.createSelectorQuery()
来代替。
(2)将选择器的选取范围更改为自定义组件 component
内。(初始时,选择器仅选取页面范围的节点,不会选取任何自定义组件中的节点)。
(3)在当前页面下选择第一个匹配选择器 selector
的节点。返回一个 NodesRef
对象实例,可以用于获取节点信息。
(4)添加节点的布局位置的查询请求。相对于显示区域,以像素为单位。其功能类似于 DOM 的 getBoundingClientRect
。返回 NodesRef
对应的 SelectorQuery
。
回调函数,在执行 SelectorQuery.exec
方法后,节点信息会在 callback
中返回。
(5)执行所有的请求。请求结果按请求次序构成数组,在callback
的第一个参数中返回。
滑动组件slide-view没有滑动的初始样式为:
向左滑动的样式为:
因为class和style样式文件对于js逻辑而言不重要,因此删去以免影响js的阅读。
<!--components/slide-view/index.wxml-->
<movable-area class="" style="">
<movable-view direction="horizontal" class=""
out-of-bounds="{{out}}" damping="20" disabled="{{disabled}}"
x = "{{x}}" style=""
intertia bindtouchend="onTouchEnd" bindtouchstart="onTouchStart" bindchange="onChange">
<view class="" style="">
<slot name="left"></slot>
</view>
<view class="" mut-bind:tap="onRightTap" style="">
<slot name="right"></slot>
</view>
</movable-view>
</movable-area>
movable-area
表示中间的元素移动的范围。开发者使用时可以对其width
和height
进行设置。movable-view
表示可移动的视图容器,在页面中可以拖拽滑动。movable-view
必须在 movable-area
组件中,并且必须是直接子节点,否则不能移动。(1)direction
设置移动方向—水平移动;(2)out-of-bounds
表示是否允许移动出范围—false表示不允许;(3)damping
表示阻尼系数,值越大表示;(4)disabled
表示是否禁用移动模块—true的就是禁用移动;(5)x,y
属性表示各方向上的移动—定义x轴方向的偏移,如果x的值不在可移动范围内,会自动移动到可移动范围;改变x的值会触发动画;(6)intertia
表示滑块movable-view是否带有惯性;(7)bindtouchstart
和bindtouchend
表示手指触摸动作开始(手指放上去开始直到结束)、手指触摸动作结束(手指拿开)。bind
属于事件参考【小程序事件详情】;(8)两个view中包裹slot
插槽,用来插入开发者需要显示的组件。外部传入的数据列表properties:
// 单位px
const _windowWidth = wx.getSystemInfoSync().windowWidth;
/**
* 组件的属性列表
*/
properties: {
// 组件显示区域的上边距
top: {
type: Number,
value: 0
},
// 组件显示区域的上边距
bottom: {
type: Number,
value: 0
},
// 组件显示区域的宽度,默认按全屏宽度
width: {
type: Number,
value: _windowWidth
},
// 组件显示区域的高度
height: {
type: Number,
value: 100
},
// 组件显示滑动区域的宽度,默认不能滑动
slideWidth: {
type: Number,
value: 0
},
// 滑动的阈值 单位px
threshold: {
type: Number,
value: 0
},
// 禁用移动
disabled: {
type: Boolean,
value: false
},
// 自动关闭
autoClose: {
type: Boolean,
value: false
}
},
使用x的值来控制movable-view的滑动距离。
/**
* 组件的初始数据
*/
data: {
// 默认是组件的宽度 单位rpx
viewWidth: _windowWidth,
// movable-view偏移量
x: 0,
// movable-view是否可以出界
out: false
},
ready() {
this.updateRight();
},
该方法是在组件生命周期的ready()方法中直接调用。主要是组件在页面布局之后调用。
updateRight()
方法的作用:
获取右侧滑动区域宽度 viewWidth(不同机型的单位px转换成rpx)、并指定一个滑动阈值_threshold(一个私有变量)。
// 获取右侧滑动区域宽度、并指定一个滑动阈值。
updateRight() {
const me = this;
// 创建节点查询器,并且将选择器选取范围更改在component组件内
const query = wx.createSelectorQuery().in(this);
// 在当前组件下名叫right的节点。返回一个 NodesRef 对象实例,可以用于获取节点信息
query.select('.right').boundingClientRect(function(res){
me._slideWidth = res.width;
// 右侧区域小于50px就按自身宽度,大于则按50px
let width = res.width <= 50 ? res.width : 50;
// 滑动阈值如果设定就按设定的,没有就按width的长度
me._threshold = me.properties.threshold ? me.properties.threshold : width;
// 不同机型的px换算为rpx
me._viewWidth = me.data.width + res.width * (750 / _windowWidth);
me.setData({
viewWidth: me._viewWidth
});
})).exec()
},
const { title} = this
写法是ES6的赋值语句简写,等同于const title = this.propeties.title;
当然参数名称是需要一致的,都必须为title。// ES6的写法
const {
_endX,
_startX,
_threshold
} = this;
// 等同于一般写法
const _endX = this._endX;
const _startX = this._startX;
const _threshold = this._threshold;
(1)onTouchStart()
是将当前x
坐标赋值给私有变量_startX
,保存x起点坐标。
(2)onTouchEnd()
是考虑各种滑动情况,然后自动完成滑动操作。
主要考虑是以下几个逻辑:
当right已经打开,仍然向左滑动这时使用onChange()
方法来设置movable-view
的出界情况:
// 根据滑动的范围设定是否运行movable-view出界
onChange(e) {
if (!this.data.out && e.detail.x < -this._threshold) {
this.setData({
out: true
});
} else if (this.data.out && e.detail.x >= -this._threshold) {
this.setData({
out: false
});
}
},
// 设置点击起始x的坐标
onTouchStart(e) {
this._startX = e.changedTouches[0].pageX;
},
// 滑动范围超过阈值则自动完成剩余滑动
onTouchEnd(e) {
if(this.properties.disabled) return; // 禁止滑动
this._endX = e.changedTouches[0].pageX;
this._length = this._endX - this._startX;
const {
_endX,
_startX,
_threshold
} = this;
// 不管right有无打开,往右滑超过阈值,都关闭right
if(this._length > _threshold) {
this.setData({
popup: false,
x: 0
});
this.onCloseTap();
}
// 向左滑动超过阈值,就自动打开right
if(_startX - _endX >= _threshold) {
this.setData({
x: -this._slideWidth,
popup: true
});
this.onOpenTap();
}else if (_startX - _endX < _threshold && _startX - _endX > 0 && this.data.popup != true) { // 没有打开right,左滑小距离
this.setData({
x: 0
});
this.onCloseTap();
} else if (_endX - _startX >= _threshold) { // 已经打开right,往右滑大距离
this.setData({
x: 0
});
this.onCloseTap();
}else if (_endX - _startX < _threshold && _endX - _startX > 0) { // 已经打开right,还往右滑小距离
this.setData({
x: -this._slideWidth
});
this.onOpenTap();
}
},
// 点击 右边区域
onRightTap() {
let detail = 'click right';
let option = { bubbles: true, composed: true };
// 如果自动关闭
if (this.properties.autoClose) {
this.setData({
popup: false,
x: 0
});
this.onCloseTap();
}
this.triggerEvent('slidetap', detail, option)
},
点击右边的不同text我采用了一种比较笨的办法,后续有改进方法再替换:
我给每个text
绑定tap
方法,绑定数据text
,然后在rightClick
方法中设置页面变量text
的值,从slidetap
方法中获取页面中变量text
的值。
<text class="like" bind:tap="rightClick" data-text="{{like}}">{{like}}</text>
<text class="share" bind:tap="rightClick" data-text="{{share}}">{{share}}</text>
<text class="del" bind:tap="rightClick" data-text="{{del}}">{{del}}</text>
slideTap(event){
console.log(event.currentTarget.dataset.id);
console.log(this.data.text);
},
rightClick(event){
this.data.text = event.currentTarget.dataset.text;
},