曹睿
2 天以前 aca7aa9ce32acc4c8795342f945b027d3bc9f62a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
// [z-paging]微信小程序、QQ小程序、app-vue、h5上使用wxs实现自定义下拉刷新,降低逻辑层与视图层的通信折损,提升性能
 
var currentDis = 0;
var isPCFlag = -1;
var startY = -1;
 
// 监听js层传过来的数据
function propObserver(newVal, oldVal, ownerIns, ins) {
    var state = ownerIns.getState() || {};
    state.currentIns = ins;
    var dataset = ins.getDataset();
    var loading = dataset.loading == true;
    // 如果是下拉刷新结束,更新transform
    if (newVal && newVal.indexOf('end') != -1) {
        var transition = newVal.split('end')[0];
        _setTransform('translateY(0px)', ins, false, transition);
        state.moveDis = 0;
        state.oldMoveDis = 0;
        currentDis = 0;
    } else if (newVal && newVal.indexOf('begin') != -1) {
        // 如果是下拉刷新开始,更新transform
        var refresherThreshold = ins.getDataset().refresherthreshold;
        _setTransformValue(refresherThreshold, ins, state, false);
    }
}
 
// touch开始
function touchstart(e, ownerIns) {
    var ins = _getIns(ownerIns);
    var state = {};
    var dataset = {};
    ownerIns.callMethod('_handleListTouchstart');
    if (ins) {
        state = ins.getState();
        dataset = ins.getDataset();
        if (_touchDisabled(e, ins, 0)) return;
    }
    var isTouchEnded = state.isTouchEnded;
    state.oldMoveDis = 0;
    var touch = _getTouch(e);
    var loading = _isTrue(dataset.loading);
    state.startY = touch.touchY;
    startY = state.startY;
    state.lastTouch = touch;
    if (!loading && isTouchEnded) {
        state.isTouchmoving = false;
    }
    state.isTouchEnded = false;
    // 通知js层touch开始
    ownerIns.callMethod('_handleRefresherTouchstart', touch);
}
 
// touch中
function touchmove(e, ownerIns) {
    var touch = _getTouch(e);
    var ins = _getIns(ownerIns);
    var dataset = ins.getDataset();
    var refresherThreshold = dataset.refresherthreshold;
    var refresherF2Threshold = dataset.refresherf2threshold;
    var refresherF2Enabled = _isTrue(dataset.refresherf2enabled);
    var isIos = _isTrue(dataset.isios);
    var state = ins.getState();
    var watchTouchDirectionChange = _isTrue(dataset.watchtouchdirectionchange);
    var moveDisObj = {};
    var moveDis = 0;
    var prevent = false;
    // 如果需要监听touch方向的改变
    if (watchTouchDirectionChange) {
        moveDisObj = _getMoveDis(e, ins);
        moveDis = moveDisObj.currentDis;
        prevent = moveDisObj.isDown;
        var direction = prevent ? 'top' : 'bottom';
        // 确保只在touch方向改变时通知一次js层,而不是touchmove中持续通知
        if (prevent == state.oldTouchDirection && prevent != state.oldEmitedTouchDirection) {
            ownerIns.callMethod('_handleTouchDirectionChange', { direction: direction }); 
            state.oldEmitedTouchDirection = prevent;
        }
        state.oldTouchDirection = prevent;
    }
    // 判断是否允许下拉刷新
    if (_touchDisabled(e, ins, 1)) {
        _handlePullingDown(state, ownerIns, false);
        return true;
    }
    // 判断下拉刷新的角度是否在要求范围内
    if (!_getAngleIsInRange(e, touch, state, dataset)) {
        _handlePullingDown(state, ownerIns, false);
        return true;
    }
    moveDisObj = _getMoveDis(e, ins);
    moveDis = moveDisObj.currentDis;
    prevent = moveDisObj.isDown;
    if (moveDis < 0) {
        // moveDis小于0,将transform重置为0
        _setTransformValue(0, ins, state, false);
        _handlePullingDown(state, ownerIns, false);
        return true;
    }
    if (prevent && !state.disabledBounce) {
        // 如果是用户下拉并且需要触发下拉刷新,需要通知js层将列表禁止滚动,防止在下拉刷新过程中列表也可以滚动导致的下拉刷新偏移过大的问题(在下拉刷新过程中仅通知一次)
        ownerIns.callMethod('_handleScrollViewBounce', { bounce: false });
        state.disabledBounce = true;
        _handlePullingDown(state, ownerIns, prevent);
        return !prevent;
    }
    // 更新transform
    _setTransformValue(moveDis, ins, state, false);
    var oldRefresherStatus = state.refresherStatus;
    var oldIsTouchmoving = _isTrue(dataset.oldistouchmoving);
    var hasTouchmove = _isTrue(dataset.hastouchmove);
    var isTouchmoving = state.isTouchmoving;
    state.refresherStatus = moveDis >= refresherThreshold ? (refresherF2Enabled && moveDis > refresherF2Threshold ? 'goF2' : 'releaseToRefresh') : 'default';
    if (!isTouchmoving) {
        state.isTouchmoving = true;
        isTouchmoving = true;
    }
    if (state.isTouchEnded) {
        state.isTouchEnded = false;
    }
    // 如果需要实时监听下拉位置偏移,则需要实时通知js层,此操作会使wxs层与js层频繁通信从而导致在一些性能较差设备中下拉刷新卡顿
    if (hasTouchmove) {
        ownerIns.callMethod('_handleWxsPullingDown', { moveDis: moveDis, diffDis: moveDisObj.diffDis });
    }
    // 在下拉刷新状态改变时通知js层
    if (oldRefresherStatus == undefined || oldRefresherStatus != state.refresherStatus || oldIsTouchmoving != isTouchmoving) {
        ownerIns.callMethod('_handleRefresherTouchmove', moveDis, touch);
    }
    _handlePullingDown(state, ownerIns, prevent);
    return !prevent;
}
 
// touch结束
function touchend(e, ownerIns) {
    var touch = _getTouch(e);
    var ins = _getIns(ownerIns);
    var dataset = ins.getDataset();
    var state = ins.getState();
    if (state.disabledBounce) {
        // 通知js允许列表滚动
        ownerIns.callMethod('_handleScrollViewBounce', { bounce: true });
        state.disabledBounce = false;
    }
    if (_touchDisabled(e, ins, 2)) return;
    state.reachMaxAngle = true;
    state.hitReachMaxAngleCount = 0;
    state.fixedIsTopHitCount = 0;
    if (!state.isTouchmoving) return;
    var oldRefresherStatus = state.refresherStatus;
    var oldMoveDis = state.moveDis;
    var refresherThreshold = ins.getDataset().refresherthreshold;
    var moveDis = _getMoveDis(e, ins).currentDis;
    if (!(moveDis >= refresherThreshold && oldRefresherStatus === 'releaseToRefresh')) {
        state.isTouchmoving = false;
    }
    // 通知js层touch结束
    ownerIns.callMethod('_handleRefresherTouchend', moveDis);
    state.isTouchEnded = true;
    if (oldMoveDis < refresherThreshold) return;
    var animate = false;
    if (moveDis >= refresherThreshold) {
        moveDis = refresherThreshold;
        animate = true;
    }
    _setTransformValue(moveDis, ins, state, animate);
}
 
// #ifdef H5
// 判断是否是pc平台
function isPC() {
    if (!navigator) return false;
    if (isPCFlag != -1) return isPCFlag;
    var agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"];
    isPCFlag = agents.every(function(item) { return navigator.userAgent.indexOf(item) < 0 });
    return isPCFlag;
}
 
var movable = false;
 
// 在pc平台监听mousedown、mousemove、mouseup等相关事件并转为对应touch事件处理,使得在pc平台也支持通过鼠标进行下拉刷新
 
function mousedown(e, ins) {
    if (!isPC()) return;
    touchstart(e, ins);
    movable = true;
}
 
function mousemove(e, ins) {
    if (!isPC() || !movable) return;
    touchmove(e, ins);
}
 
function mouseup(e, ins) {
    if (!isPC()) return;
    touchend(e, ins);
    movable = false;
}
 
function mouseleave(e, ins) {
    if (!isPC()) return;
    movable = false;
}
// #endif
 
 
// 修改视图层transform
function _setTransformValue(value, ins, state, animate) {
    value = value || 0;
    if (state.moveDis == value) return;
    state.moveDis = value;
    _setTransform('translateY(' + value + 'px)', ins, animate, '');
}
 
// 设置视图层transform,直接在视图层操作下拉刷新,使得js层不需要频繁和视图层通信,从而大大提升下拉刷新性能
function _setTransform(transform, ins, animate, transition) {
    var dataset = ins.getDataset();
    if (_isTrue(dataset.refreshernotransform)) return;
    transform = transform == 'translateY(0px)' ? 'none' : transform;
    ins.requestAnimationFrame(function() {
        var stl = { 'transform': transform };
        if (animate) {
            stl['transition'] = 'transform .1s linear';
        }
        if (transition.length) {
            stl['transition'] = transition;
        }
        ins.setStyle(stl);
    })
}
 
// 进一步处理下拉刷新的偏移数据
function _getMoveDis(e, ins) {
    var state = ins.getState();
    var refresherThreshold = parseFloat(ins.getDataset().refresherthreshold);
    var refresherOutRate = parseFloat(ins.getDataset().refresheroutrate);
    var refresherPullRate = parseFloat(ins.getDataset().refresherpullrate);
    var touch = _getTouch(e);
    var currentStartY = !state.startY || state.startY == 'NaN' ? startY : state.startY;
    var moveDis = touch.touchY - currentStartY;
    var oldMoveDis = state.oldMoveDis || 0;
    state.oldMoveDis = moveDis;
    // 获取当前下拉刷新位置与上次的偏移量
    var diffDis = moveDis - oldMoveDis;
    if (diffDis > 0) {
        // 对偏移量进行进一步处理,通过refresherPullRate等配置进行约束
        diffDis = diffDis * refresherPullRate;
        if (currentDis > refresherThreshold) {
            diffDis = diffDis * (1 - refresherOutRate);
        }
    }
    // 控制diffDis过大的情况,比如进入页面突然猛然下拉,此时diffDis不应进行太大的偏移
    diffDis = diffDis > 100 ? diffDis / 100 : (diffDis > 20 ? diffDis / 2.2 : diffDis);
    currentDis += diffDis;
    currentDis = Math.max(0, currentDis);
    return {
        currentDis: currentDis,
        diffDis: diffDis,
        isDown: diffDis > 0
    };
}
 
// 获取经过统一格式包装的当前touch对象
function _getTouch(e) {
    var touch = e;
    if (e.touches && e.touches.length) {
        touch = e.touches[0];
    } else if (e.changedTouches && e.changedTouches.length) {
        touch = e.changedTouches[0];
    } else if (e.datail && e.datail != {}) {
        touch = e.datail;
    }
    return {
        touchX: touch.clientX,
        touchY: touch.clientY
    };
}
 
// 获取当前currentIns
function _getIns(ownerIns) {
    var ins = ownerIns.getState().currentIns;
    if (!ins) {
        ownerIns.callMethod('_handlePropUpdate');
    }
    return ins;
}
 
// 判断当前状态是否允许下拉刷新
function _touchDisabled(e, ins, processTag) {
    var dataset = ins.getDataset();
    var state = ins.getState();
    var loading = _isTrue(dataset.loading);
    var useChatRecordMode = _isTrue(dataset.usechatrecordmode);
    var refresherEnabled = _isTrue(dataset.refresherenabled);
    var useCustomRefresher = _isTrue(dataset.usecustomrefresher);
    var usePageScroll = _isTrue(dataset.usepagescroll);
    var pageScrollTop = parseFloat(dataset.pagescrolltop);
    var scrollTop = parseFloat(dataset.scrolltop);
    var finalScrollTop = usePageScroll ? pageScrollTop : scrollTop;
    var fixedIsTop = false;
    // 是否要处理滚动到顶部scrollTop不为0时候的容错,为解决在安卓中scroll-view有概率滚动到顶部时scrollTop不为0导致下拉刷新判断异常,但此方案会导致某些情况(例如滚动到距离顶部10px处)下拉抖动,因此改为通过获取zp-scroll-view的节点信息中的scrollTop进行验证的方案
    var handleFaultTolerantMove = false;
    if (handleFaultTolerantMove && finalScrollTop == (state.startScrollTop || 0) && finalScrollTop <= 105) {
        fixedIsTop = true;
    }
    var fixedIsTopHitCount = state.fixedIsTopHitCount || 0;
    if (fixedIsTop) {
        fixedIsTopHitCount ++;
        if (fixedIsTopHitCount <= 2) {
            fixedIsTop = false;
        }
        state.fixedIsTopHitCount = fixedIsTopHitCount;
    } else {
        state.fixedIsTopHitCount = 0;
    }
    if (handleFaultTolerantMove && processTag === 0) {
        state.startScrollTop = finalScrollTop || 0;
    }
    if (handleFaultTolerantMove && processTag === 2) {
        fixedIsTop = true;
    }
    return loading || useChatRecordMode || !refresherEnabled || !useCustomRefresher || 
    ((usePageScroll && useCustomRefresher && pageScrollTop > 5) && !fixedIsTop) || 
    ((!usePageScroll && useCustomRefresher && scrollTop > 5) && !fixedIsTop);
}
 
// 判断下拉刷新的角度是否在要求范围内
function _getAngleIsInRange(e, touch, state, dataset) {
    var maxAngle = dataset.refreshermaxangle;
    var refresherAecc = _isTrue(dataset.refresheraecc);
    var lastTouch = state.lastTouch;
    var reachMaxAngle = state.reachMaxAngle;
    var moveDis = state.oldMoveDis;
    if (!lastTouch) return true;
    if (maxAngle >= 0 && maxAngle <= 90 && lastTouch) {
        // 考虑下拉刷新手势由水平移动转为垂直方向移动的情况,此时不应当只判断垂直方向角度是否符合要求,应当直接禁止以避免在swiper中使用下拉刷新时,横向切换swiper途中手未离开屏幕还可以下拉刷新的问题
        if ((!moveDis || moveDis < 1) && !refresherAecc && reachMaxAngle != null && !reachMaxAngle) return false;
        var x = Math.abs(touch.touchX - lastTouch.touchX);
        var y = Math.abs(touch.touchY - lastTouch.touchY);
        var z = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
        if ((x || y) && x > 1) {
            // 获取下拉刷新前后两次位移的角度
            var angle = Math.asin(y / z) / Math.PI * 180;
            if (angle < maxAngle) {
                // 如果角度小于配置要求,则return,同时通过hitReachMaxAngleCount控制角度判断的灵敏程度以最大程度兼容各种使用场景
                var hitReachMaxAngleCount = state.hitReachMaxAngleCount || 0;
                state.hitReachMaxAngleCount = ++hitReachMaxAngleCount;
                if (state.hitReachMaxAngleCount > 2) {
                    state.lastTouch = touch;
                    state.reachMaxAngle = false;
                }
                return false;
            }
        }
    }
    state.lastTouch = touch;
    return true;
}
 
// 进一步处理是否在下拉刷新并通知js层
function _handlePullingDown(state, ins, onPullingDown) {
    var oldOnPullingDown = state.onPullingDown || false;
    if (oldOnPullingDown != onPullingDown) {
        ins.callMethod('_handleWxsPullingDownStatusChange', onPullingDown);
    }
    state.onPullingDown = onPullingDown;
}
 
// 判断js层传过来的值是否为true
function _isTrue(value) {
    value = (typeof(value) === 'string' ? JSON.parse(value) : value) || false;
    return value == true || value == 'true';
}
 
module.exports = {
    touchstart: touchstart,
    touchmove: touchmove,
    touchend: touchend,
    mousedown: mousedown,
    mousemove: mousemove,
    mouseup: mouseup,
    mouseleave: mouseleave,
    propObserver: propObserver
}