提交 db05fa70 编写于 作者: Z zy19940510

Merge branch 'next' of https://github.com/jdf2e/nutui into next

......@@ -323,6 +323,15 @@ module.exports = {
sort: 16,
show: true,
author: 'Jerry'
},
{
name: 'PullRefresh',
type: 'component',
cName: '下拉刷新',
desc: '下拉刷新',
sort: 16,
show: true,
author: 'yangxiaolu3'
}
]
},
......
<template>
<div class="demo">
<nut-pullrefresh @refresh="refresh" :useWindow="false" containerId="pull">
<div class="content" id="pull">
<div class="main">
<div class="text-data">我是测试数据1</div>
<div class="text-data">我是测试数据2</div>
<div class="text-data">我是测试数据3</div>
<div class="text-data">我是测试数据4</div>
<div class="text-data">我是测试数据5</div>
<div class="text-data">我是测试数据6</div>
<div class="text-data">我是测试数据7</div>
<div class="text-data">我是测试数据8</div>
<div class="text-data">我是测试数据9</div>
<div class="text-data">我是测试数据10</div>
<div class="text-data">我是测试数据11</div>
<div class="text-data">我是测试数据12</div>
<div class="text-data">我是测试数据13</div>
<div class="text-data">我是测试数据14</div>
<div class="text-data">我是测试数据15</div>
<div class="text-data">我是测试数据16</div>
<div class="text-data">我是测试数据17</div>
<div class="text-data">我是测试数据18</div>
<div class="text-data">我是测试数据19</div>
<div class="text-data">我是测试数据20</div>
<div class="text-data">我是测试数据21</div>
<div class="text-data">我是测试数据22</div>
<div class="text-data">我是测试数据23</div>
<div class="text-data">我是测试数据24</div>
</div>
</div>
</nut-pullrefresh>
</div>
</template>
<script lang="ts">
import { createComponent } from '@/utils/create';
const { createDemo } = createComponent('pullrefresh');
export default createDemo({
props: {},
setup() {
const refresh = done => {
setTimeout(() => {
done();
}, 1000);
};
return { refresh };
}
});
</script>
<style lang="scss" scoped>
.content {
height: 100%;
overflow: auto;
.main {
padding: 10px 0;
background: #f00;
}
}
</style>
# pullrefresh组件
### 介绍
基于 xxxxxxx
### 安装
## 代码演示
### 基础用法1
## API
### Props
| 参数 | 说明 | 类型 | 默认值 |
|--------------|----------------------------------|--------|------------------|
| name | 图标名称或图片链接 | String | - |
| color | 图标颜色 | String | - |
| size | 图标大小,如 '20px' '2em' '2rem' | String | - |
| class-prefix | 类名前缀,用于使用自定义图标 | String | 'nutui-iconfont' |
| tag | HTML 标签 | String | 'i' |
### Events
| 事件名 | 说明 | 回调参数 |
|--------|----------------|--------------|
| click | 点击图标时触发 | event: Event |
\ No newline at end of file
view {
display: block;
}
.nut-pullrefresh {
position: relative;
height: 100%;
}
.nut-pullrefresh .pullrefresh-top {
position: absolute;
left: 0;
width: 100%;
height: 50px;
overflow: hidden;
color: #969799;
font-size: 14px;
line-height: 50px;
text-align: center;
-webkit-transform: translateY(-100%);
transform: translateY(-100%);
}
.nut-pullrefresh .pullrefresh-content {
height: 100%;
overflow: auto;
background: #fff;
}
.nut-pullrefresh .pullrefresh-bottom {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 0px;
overflow: hidden;
color: #969799;
font-size: 14px;
line-height: 50px;
text-align: center;
-webkit-transform: translateY(100%);
transform: translateY(100%);
}
view{display:block}.nut-pullrefresh{position:relative;height:100%}.nut-pullrefresh .pullrefresh-top{position:absolute;left:0;width:100%;height:50px;overflow:hidden;color:#969799;font-size:14px;line-height:50px;text-align:center;-webkit-transform:translateY(-100%);transform:translateY(-100%)}.nut-pullrefresh .pullrefresh-content{height:100%;overflow:auto;background:#fff}.nut-pullrefresh .pullrefresh-bottom{position:absolute;left:0;bottom:0;width:100%;height:0px;overflow:hidden;color:#969799;font-size:14px;line-height:50px;text-align:center;-webkit-transform:translateY(100%);transform:translateY(100%)}
view {
display: block;
}
.nut-pullrefresh {
position: relative;
height: 100%;
.pullrefresh-top {
position: absolute;
left: 0;
width: 100%;
height: 50px;
overflow: hidden;
color: #969799;
font-size: 14px;
line-height: 50px;
text-align: center;
-webkit-transform: translateY(-100%);
transform: translateY(-100%);
}
.pullrefresh-content {
height: 100%;
overflow: auto;
background: #fff;
}
.pullrefresh-bottom {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 0px;
overflow: hidden;
color: #969799;
font-size: 14px;
line-height: 50px;
text-align: center;
-webkit-transform: translateY(100%);
transform: translateY(100%);
}
}
<template>
<view
class="nut-pullrefresh"
ref="scroller"
:style="getStyle"
@touchstart="touchStart"
@touchmove="touchMove"
@touchend="touchEnd"
>
<view class="pullrefresh-top">
<template v-if="status == 'loading' && reachTop && distance > 0">
加载中...
</template>
<template v-if="status == 'pulling' && reachTop && distance > 0">
下拉刷新...
</template>
<template v-if="status == 'loosing' && reachTop && distance > 0">
释放刷新...
</template>
</view>
<view class="pullrefresh-content" ref="pull">
<slot></slot>
</view>
<view class="pullrefresh-bottom" :style="getBottomStyle">
<template v-if="status == 'loading' && reachBottom && distance < 0">
加载中...
</template>
<template v-if="status == 'pulling' && reachBottom && distance < 0">
下拉刷新...
</template>
<template v-if="status == 'loosing' && reachBottom && distance < 0">
释放刷新...
</template>
</view>
</view>
</template>
<script lang="ts">
import { ref, toRefs, reactive, onMounted, computed, CSSProperties } from 'vue';
import { createComponent } from '@/utils/create';
import { useTouch } from './use-touch';
import { preventDefault } from './util';
const { componentName, create } = createComponent('pullrefresh');
export default create({
props: {
useWindow: {
type: Boolean,
default: true
},
containerId: {
type: String,
default: ''
}
},
components: {},
emits: ['refresh'],
setup(props, { emit }) {
console.log('componentName', componentName);
const { containerId, useWindow } = toRefs(props);
const reachTop = ref(false);
const reachBottom = ref(false);
const state = reactive({
status: 'normal',
distance: 0,
duration: 0
});
let scrollEl: HTMLElement = document.documentElement || document.body;
const scroller = ref<null | HTMLElement>(null);
const touch = useTouch();
const getStyle = computed(() => {
let style: CSSProperties = {};
if (
(reachTop.value && touch.deltaY.value > 0 && touch.isVertical()) ||
(reachBottom.value && touch.deltaY.value < 0 && touch.isVertical())
) {
style = {
transitionDuration: `${state.duration}ms`,
transform: state.distance
? `translate3d(0,${state.distance}px, 0)`
: `translate3d(0,0,0)`
};
}
return style;
});
const getBottomStyle = computed(() => {
let style: CSSProperties = {};
if (reachBottom.value && touch.deltaY.value < 0 && touch.isVertical()) {
const dis = Math.abs(state.distance) < 50 ? -state.distance : 50;
style = {
height: dis + 'px'
};
}
return style;
});
const setStatus = (distance: number, isLoading?: boolean) => {
state.distance = distance;
if (isLoading) {
state.status = 'loading';
} else if (distance === 0) {
state.status = 'normal';
} else if (Math.abs(distance) < 50) {
state.status = 'pulling';
} else {
state.status = 'loosing';
}
};
/** 获取监听自定义滚动节点 */
const getParentElement = el => {
if (containerId.value != '') {
return document.querySelector(`#${containerId.value}`);
}
return el && el.parentNode;
};
/** 生命周期 首次加载 */
onMounted(() => {
const parentElement = getParentElement(scroller);
let scrollElCopy = document.documentElement || document.body;
if (useWindow.value === false) {
scrollElCopy = parentElement;
}
scrollEl = scrollElCopy;
});
const ease = (distance: number) => {
const headHeight = 50;
if (distance > headHeight) {
if (distance < headHeight * 2) {
distance = headHeight + (distance - headHeight) / 2;
} else {
distance = headHeight * 1.5 + (distance - headHeight * 2) / 4;
}
}
return Math.round(distance);
};
const refreshDone = () => {
setStatus(0);
};
const touchStart = event => {
/** 判断滚动条是否在顶部 */
const top = 'scrollTop' in scrollEl ? scrollEl.scrollTop : 0;
reachTop.value = Math.max(top, 0) == 0 ? true : false;
if (reachTop.value) {
state.duration = 0;
touch.start(event);
}
const { scrollHeight, clientHeight, scrollTop } = scrollEl;
/** 判断滚动条是否在底部*/
reachBottom.value =
clientHeight + scrollTop == scrollHeight ? true : false;
if (reachBottom.value) {
state.duration = 0;
touch.start(event);
}
};
const touchMove = event => {
const { deltaY } = touch;
touch.move(event);
if (reachTop.value && deltaY.value >= 0 && touch.isVertical()) {
preventDefault(event);
setStatus(ease(deltaY.value));
}
if (reachBottom.value && deltaY.value < 0 && touch.isVertical()) {
preventDefault(event);
setStatus(ease(deltaY.value));
}
};
const touchEnd = () => {
if (reachTop.value && touch.deltaY.value > 0) {
if (state.status === 'loosing') {
setStatus(50, true);
emit('refresh', refreshDone);
} else {
setStatus(0);
}
}
if (reachBottom.value && touch.deltaY.value < 0) {
if (state.status === 'loosing') {
setStatus(-50, true);
emit('refresh', refreshDone);
} else {
setStatus(0);
}
}
};
return {
scroller,
touchStart,
touchMove,
touchEnd,
getStyle,
reachBottom,
reachTop,
getBottomStyle,
...toRefs(state)
};
}
});
</script>
<style lang="scss">
@import 'index.scss';
</style>
import { ref } from 'vue';
const MIN_DISTANCE = 10;
type Direction = '' | 'vertical' | 'horizontal';
function getDirection(x: number, y: number) {
if (x > y && x > MIN_DISTANCE) {
return 'horizontal';
}
if (y > x && y > MIN_DISTANCE) {
return 'vertical';
}
return '';
}
export function useTouch() {
const startX = ref(0);
const startY = ref(0);
const deltaX = ref(0);
const deltaY = ref(0);
const offsetX = ref(0);
const offsetY = ref(0);
const direction = ref<Direction>('');
const isVertical = () => direction.value === 'vertical';
const isHorizontal = () => direction.value === 'horizontal';
const reset = () => {
deltaX.value = 0;
deltaY.value = 0;
offsetX.value = 0;
offsetY.value = 0;
direction.value = '';
};
const start = ((event: TouchEvent) => {
reset();
startX.value = event.touches[0].clientX;
startY.value = event.touches[0].clientY;
}) as EventListener;
const move = ((event: TouchEvent) => {
const touch = event.touches[0];
deltaX.value = touch.clientX - startX.value;
deltaY.value = touch.clientY - startY.value;
offsetX.value = Math.abs(deltaX.value);
offsetY.value = Math.abs(deltaY.value);
if (!direction.value) {
direction.value = getDirection(offsetX.value, offsetY.value);
}
}) as EventListener;
return {
move,
start,
reset,
startX,
startY,
deltaX,
deltaY,
offsetX,
offsetY,
direction,
isVertical,
isHorizontal
};
}
export function preventDefault(event: Event, isStopPropagation?: boolean) {
/* istanbul ignore else */
if (typeof event.cancelable !== 'boolean' || event.cancelable) {
event.preventDefault();
}
if (isStopPropagation) event.stopPropagation();
}
export function trigger(target: Element, type: string) {
const inputEvent = document.createEvent('HTMLEvents');
inputEvent.initEvent(type, true, true);
target.dispatchEvent(inputEvent);
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册