|
|
@@ -1,13 +1,17 @@
|
|
|
<template>
|
|
|
- <view-block
|
|
|
+ <scroll-view
|
|
|
:class="classes"
|
|
|
- ref="scroller"
|
|
|
+ scrollY="true"
|
|
|
+ style="height: 100%"
|
|
|
+ id="scroller"
|
|
|
+ @scrolltolower="lower"
|
|
|
+ @scroll="scroll"
|
|
|
@touchstart="touchStart"
|
|
|
@touchmove="touchMove"
|
|
|
@touchend="touchEnd"
|
|
|
>
|
|
|
- <view-block class="nut-infinite-top" ref="refreshTop" :style="getStyle">
|
|
|
- <view-block class="top-box">
|
|
|
+ <view-block class="nut-infinite-top" :style="getStyle">
|
|
|
+ <view-block class="top-box" id="refreshTop">
|
|
|
<nut-icon class="top-img" :name="pullIcon"></nut-icon>
|
|
|
<view-block class="top-text">{{ pullTxt }}</view-block>
|
|
|
</view-block>
|
|
|
@@ -28,20 +32,14 @@
|
|
|
<view-block class="tips">{{ loadMoreTxt }}</view-block>
|
|
|
</template>
|
|
|
</view-block>
|
|
|
- </view-block>
|
|
|
+ </scroll-view>
|
|
|
</template>
|
|
|
<script lang="ts">
|
|
|
-import {
|
|
|
- toRefs,
|
|
|
- onMounted,
|
|
|
- onUnmounted,
|
|
|
- reactive,
|
|
|
- computed,
|
|
|
- CSSProperties
|
|
|
-} from 'vue';
|
|
|
+import { toRefs, onMounted, reactive, computed, CSSProperties } from 'vue';
|
|
|
import { createComponent } from './../../../../../../packages/utils/create';
|
|
|
const { componentName, create } = createComponent('infiniteloading');
|
|
|
import Icon from './../icon/index.taro.vue';
|
|
|
+import Taro from '@tarojs/taro';
|
|
|
export default create({
|
|
|
props: {
|
|
|
hasMore: {
|
|
|
@@ -97,12 +95,11 @@ export default create({
|
|
|
},
|
|
|
setup(props, { emit, slots }) {
|
|
|
const state = reactive({
|
|
|
- scrollEl: window as Window | HTMLElement | (Node & ParentNode),
|
|
|
- scroller: null as null | HTMLElement,
|
|
|
- refreshTop: null as null | HTMLElement,
|
|
|
- beforeScrollTop: 0,
|
|
|
- isTouching: false,
|
|
|
+ scrollHeight: 0,
|
|
|
+ scrollTop: 0,
|
|
|
isInfiniting: false,
|
|
|
+ direction: 'down',
|
|
|
+ isTouching: false,
|
|
|
refreshMaxH: 0,
|
|
|
y: 0,
|
|
|
x: 0,
|
|
|
@@ -125,113 +122,67 @@ export default create({
|
|
|
: `height 0.2s cubic-bezier(0.25,0.1,0.25,1)`
|
|
|
};
|
|
|
});
|
|
|
-
|
|
|
- const getParentElement = (el: HTMLElement) => {
|
|
|
- return !!props.containerId
|
|
|
- ? document.querySelector(`#${props.containerId}`)
|
|
|
- : el && el.parentNode;
|
|
|
- };
|
|
|
-
|
|
|
- const requestAniFrame = () => {
|
|
|
- return (
|
|
|
- window.requestAnimationFrame ||
|
|
|
- window.webkitRequestAnimationFrame ||
|
|
|
- function(callback) {
|
|
|
- window.setTimeout(callback, 1000 / 60);
|
|
|
- }
|
|
|
+ const getParentElement = el => {
|
|
|
+ return Taro.createSelectorQuery().select(
|
|
|
+ !!props.containerId ? `#${props.containerId} #${el}` : `#${el}`
|
|
|
);
|
|
|
};
|
|
|
-
|
|
|
- const getWindowScrollTop = () => {
|
|
|
- return window.pageYOffset !== undefined
|
|
|
- ? window.pageYOffset
|
|
|
- : (
|
|
|
- document.documentElement ||
|
|
|
- document.body.parentNode ||
|
|
|
- document.body
|
|
|
- ).scrollTop;
|
|
|
- };
|
|
|
-
|
|
|
- const calculateTopPosition = (el: HTMLElement): number => {
|
|
|
- return !el
|
|
|
- ? 0
|
|
|
- : el.offsetTop + calculateTopPosition(el.offsetParent as HTMLElement);
|
|
|
+ /** 获取需要滚动的距离 */
|
|
|
+ const getScrollHeight = () => {
|
|
|
+ const parentElement = getParentElement('scroller');
|
|
|
+
|
|
|
+ parentElement
|
|
|
+ .boundingClientRect(rect => {
|
|
|
+ state.scrollHeight = rect.height;
|
|
|
+ })
|
|
|
+ .exec();
|
|
|
};
|
|
|
|
|
|
- const isScrollAtBottom = () => {
|
|
|
- let offsetDistance = 0;
|
|
|
- let resScrollTop = 0;
|
|
|
- let direction = 'down';
|
|
|
- const windowScrollTop = getWindowScrollTop();
|
|
|
- if (props.useWindow) {
|
|
|
- if (state.scroller) {
|
|
|
- offsetDistance =
|
|
|
- calculateTopPosition(state.scroller) +
|
|
|
- state.scroller.offsetHeight -
|
|
|
- windowScrollTop -
|
|
|
- window.innerHeight;
|
|
|
- }
|
|
|
- resScrollTop = windowScrollTop;
|
|
|
+ /** 滚动到底部 */
|
|
|
+ const lower = () => {
|
|
|
+ if (state.direction == 'up' || !props.hasMore || state.isInfiniting) {
|
|
|
+ return false;
|
|
|
} else {
|
|
|
- const {
|
|
|
- scrollHeight,
|
|
|
- clientHeight,
|
|
|
- scrollTop
|
|
|
- } = state.scrollEl as HTMLElement;
|
|
|
-
|
|
|
- offsetDistance = scrollHeight - clientHeight - scrollTop;
|
|
|
- resScrollTop = scrollTop;
|
|
|
+ state.isInfiniting = true;
|
|
|
+ emit('load-more', infiniteDone);
|
|
|
}
|
|
|
+ };
|
|
|
|
|
|
- if (state.beforeScrollTop > resScrollTop) {
|
|
|
- direction = 'up';
|
|
|
+ const scroll = e => {
|
|
|
+ // 滚动方向
|
|
|
+ if (e.detail.scrollTop <= 0) {
|
|
|
+ // 滚动到最顶部
|
|
|
+ e.detail.scrollTop = 0;
|
|
|
+ } else if (e.detail.scrollTop >= state.scrollHeight) {
|
|
|
+ // 滚动到最底部
|
|
|
+ e.detail.scrollTop = state.scrollHeight;
|
|
|
+ }
|
|
|
+ if (
|
|
|
+ e.detail.scrollTop > state.scrollTop ||
|
|
|
+ e.detail.scrollTop >= state.scrollHeight
|
|
|
+ ) {
|
|
|
+ state.direction = 'down';
|
|
|
} else {
|
|
|
- direction = 'down';
|
|
|
+ state.direction = 'up';
|
|
|
}
|
|
|
+ state.scrollTop = e.detail.scrollTop;
|
|
|
|
|
|
- state.beforeScrollTop = resScrollTop;
|
|
|
-
|
|
|
- emit('scroll-change', resScrollTop);
|
|
|
-
|
|
|
- return offsetDistance <= props.threshold && direction == 'down';
|
|
|
+ emit('scroll-change', e.detail.scrollTop);
|
|
|
};
|
|
|
|
|
|
const infiniteDone = () => {
|
|
|
state.isInfiniting = false;
|
|
|
};
|
|
|
|
|
|
- const handleScroll = () => {
|
|
|
- requestAniFrame()(() => {
|
|
|
- if (!isScrollAtBottom() || !props.hasMore || state.isInfiniting) {
|
|
|
- return false;
|
|
|
- } else {
|
|
|
- state.isInfiniting = true;
|
|
|
- emit('load-more', infiniteDone);
|
|
|
- }
|
|
|
- });
|
|
|
- };
|
|
|
-
|
|
|
- const scrollListener = () => {
|
|
|
- state.scrollEl.addEventListener('scroll', handleScroll, props.useCapture);
|
|
|
- };
|
|
|
-
|
|
|
- const refreshDone = () => {
|
|
|
- state.distance = 0;
|
|
|
- state.isTouching = false;
|
|
|
- };
|
|
|
-
|
|
|
const touchStart = (event: TouchEvent) => {
|
|
|
- if (
|
|
|
- state.beforeScrollTop == 0 &&
|
|
|
- !state.isTouching &&
|
|
|
- props.isOpenRefresh
|
|
|
- ) {
|
|
|
+ if (state.scrollTop == 0 && !state.isTouching && props.isOpenRefresh) {
|
|
|
state.y = event.touches[0].pageY;
|
|
|
state.isTouching = true;
|
|
|
-
|
|
|
- const childHeight = ((state.refreshTop as HTMLElement)
|
|
|
- .firstElementChild as HTMLElement).offsetHeight;
|
|
|
- state.refreshMaxH = Math.floor(childHeight * 1 + 10);
|
|
|
+ getParentElement('refreshTop')
|
|
|
+ .boundingClientRect(rect => {
|
|
|
+ state.refreshMaxH = Math.floor(rect.height * 1 + 10);
|
|
|
+ })
|
|
|
+ .exec();
|
|
|
}
|
|
|
};
|
|
|
|
|
|
@@ -256,26 +207,22 @@ export default create({
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- onMounted(() => {
|
|
|
- const parentElement = getParentElement(
|
|
|
- state.scroller as HTMLElement
|
|
|
- ) as Node & ParentNode;
|
|
|
- state.scrollEl = props.useWindow ? window : parentElement;
|
|
|
-
|
|
|
- scrollListener();
|
|
|
- });
|
|
|
+ const refreshDone = () => {
|
|
|
+ state.distance = 0;
|
|
|
+ state.isTouching = false;
|
|
|
+ };
|
|
|
|
|
|
- onUnmounted(() => {
|
|
|
- state.scrollEl.removeEventListener(
|
|
|
- 'scroll',
|
|
|
- handleScroll,
|
|
|
- props.useCapture
|
|
|
- );
|
|
|
+ onMounted(() => {
|
|
|
+ setTimeout(() => {
|
|
|
+ getScrollHeight();
|
|
|
+ }, 200);
|
|
|
});
|
|
|
|
|
|
return {
|
|
|
classes,
|
|
|
...toRefs(state),
|
|
|
+ lower,
|
|
|
+ scroll,
|
|
|
touchStart,
|
|
|
touchMove,
|
|
|
touchEnd,
|