提交 ab188fde 编写于 作者: 宋成林

add Scroller

上级 5c87fb03
......@@ -359,25 +359,34 @@
"author": "Vicky.Ye"
},
{
"version":"1.0.0",
"name":"ShortPassword",
"chnName":"短密码",
"des":"短密码",
"type":"component",
"sort":"1",
"version": "1.0.0",
"name": "ShortPassword",
"chnName": "短密码",
"des": "短密码",
"type": "component",
"sort": "1",
"showDemo": true,
"author": "wangnan31"
},
{
"version":"1.0.0",
"name":"Skeleton",
"chnName":"骨架屏",
"des":"在页面上待加载区域填充灰色的占位图,本质上是界面加载过程中的过渡效果",
"type":"component",
"sort":"0",
"version": "1.0.0",
"name": "Skeleton",
"chnName": "骨架屏",
"des": "在页面上待加载区域填充灰色的占位图,本质上是界面加载过程中的过渡效果",
"type": "component",
"sort": "0",
"showDemo": true,
"author": "wangnan31"
},
{
"version": "1.0.0",
"name": "Scroller",
"chnName": "滚动",
"desc": "滚动组件",
"type": "component",
"sort": "1",
"showDemo": true,
"author": "iris"
}
]
}
\ No newline at end of file
<template>
<div class="demo-list">
<nut-noticebar
:closeMode="true"
v-if="!isMobile"
>此 Demo 在 PC 端浏览器与移动端浏览器体验差异较大,建议在 Android 或 iOS 设备上体验。</nut-noticebar>
<h4>横向用法</h4>
<div class="hor-panel">
<nut-scroller :list-data="listData"
:is-un-more="isUnMore"
:is-loading="isLoading"
@loadMore="loadMoreHor"
@jump="jump()"
>
<div slot="list" class="nut-hor-list-item" v-for="(item, index) of listData" :key="index">{{index}}</div>
<slot slot="more"><div class="nut-hor-jump-more">释放查看更多</div></slot>
</nut-scroller>
</div>
<h4>竖向用法</h4>
<div class="vert-panel">
<nut-scroller
:is-un-more="isUnMore1"
:is-loading="isLoading1"
:type="'vertical'"
@loadMore="loadMoreVert"
@pulldown="pulldown"
>
<div slot="list" class="nut-vert-list-panel">
<div class="nut-vert-list-item" v-for="(item, index) of listData1" :key="index">
{{index}}
</div>
</div>
</nut-scroller>
</div>
<h4>竖向不满一屏用法</h4>
<div class="vert-panel">
<nut-scroller
:is-un-more="isUnMore2"
:is-loading="isLoading2"
:type="'vertical'"
@loadMore="loadMoreVert2"
@pulldown="pulldown2"
>
<div slot="list" class="nut-vert-list-panel">
<div class="nut-vert-list-item" v-for="(item, index) of listData2" :key="index">
{{index}}
</div>
</div>
</nut-scroller>
</div>
</div>
</template>
<script>
export default {
data() {
return {
listData: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
listData1: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
listData2: [1, 2],
isUnMore: false,
isLoading: false,
maxPages: 3,
page: 2,
timers: null,
isUnMore1: false,
isLoading1: false,
page1: 2,
maxPages2: 1,
isUnMore2: false,
isLoading2: false,
page2: 2
};
},
methods: {
loadMoreHor() {
this.isLoading = true;
if (this.page > this.maxPages) {
this.isUnMore = true;
this.isLoading = false;
} else {
this.timer = setTimeout(() => {
this.page == ++this.page;
this.listData = [...this.listData, ...[11, 12, 13, 14, 15, 16, 17, 18, 19, 20]];
console.log(this.listData);
this.isLoading = false;
}, 300);
}
},
jump() {
console.log('跳转');
location.href = 'http://www.jd.com';
},
loadMoreVert() {
this.isLoading1 = true;
if (this.page1 > this.maxPages) {
this.isUnMore1 = true;
this.isLoading1 = false;
} else {
this.timer = setTimeout(() => {
this.page1 = ++this.page1;
this.listData1 = [...this.listData1, ...[11, 12, 13, 14, 15, 16, 17, 18, 19, 20]];
console.log(this.listData1);
this.isLoading1 = false;
}, 2000);
}
},
pulldown() {
this.isLoading1 = true;
this.timer = setTimeout(() => {
this.page1 = 2;
this.listData1 = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
this.isLoading1 = false;
this.isUnMore1 = false;
}, 2000);
},
loadMoreVert2() {
this.isLoading2 = true;
if (this.page2 > this.maxPages2) {
this.isUnMore2 = true;
this.isLoading2 = false;
} else {
this.timer = setTimeout(() => {
this.page2 = ++this.page1;
this.listData2 = [...this.listData1, ...[11, 12, 13, 14, 15, 16, 17, 18, 19, 20]];
console.log(this.listData1);
this.isLoading2 = false;
}, 2000);
}
},
pulldown2() {
this.isLoading2 = true;
this.timer = setTimeout(() => {
this.page2 = 2;
this.listData2 = [11, 12];
this.isLoading2 = false;
this.isUnMore2 = false;
}, 2000);
}
},
destroyed() {
clearTimeout(this.timer);
}
};
</script>
<style lang="scss" scoped>
.hor-panel{
height: 100px;
background-color: mix($primary-color, #FFF, 10%);
}
.nut-hor-list{
.nut-hor-list-item{
display: flex;
align-content: center;
justify-content: center;
flex-shrink: 0;
width: 200px;
height: 100px;
background-color: mix($primary-color, #FFF, 90%);
color: #FFF;
line-height: 100px;
margin-left: 10px;
}
}
.nut-hor-jump-more{
height: 100%;
width: 20px;
padding: 5px 10px;
font-size: 12px;
text-align: center;
color: $primary-color;
}
.vert-panel{
height: 400px;
padding: 10px;
background-color: mix($primary-color, #FFF, 10%);
}
.nut-vert-list-panel{
.nut-vert-list-item{
width: 100%;
height: 100px;
margin-bottom: 10px;
background-color: mix($primary-color, #FFF, 90%);
font-size: 12px;
text-align: center;
line-height: 100px;
color: #FFF;
}
}
</style>
<template>
<div class="nut-hor-scroll" rel="wrapper">
<div class="nut-hor-list" ref="list">
<slot name="list"></slot>
<div class="nut-hor-control" v-if="isUnMore && $slots.more && isShowLoadMore()">
<slot name="more"></slot>
</div>
</div>
</div>
</template>
<script>
export default {
name:'nut-hor-scroll',
props: {
listData: {
type: Array,
required: true,
default: () => []
},
lineSpacing: {
type: Number,
default: 210
},
stretch: {
type: Number,
default: 200
},
isUnMore: {
type: Boolean,
default: false
},
isLoading: {
type: Boolean,
default: false
}
},
data() {
return {
touchParams: {
startX: 0,
endX: 0,
startTime: 0,
endTime: 0
},
transformX: 0,
scrollDistance: 0,
timer: null
}
},
methods: {
isShowLoadMore() {
this.$nextTick(() => {
let wrapH = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
let listH = this.listData.length * this.lineSpacing;
if (wrapH <= listH) {
return true;
} else {
return false;
}
});
},
setTransform(translateX = 0, type, time = 500) {
if (type === 'end') {
this.$refs.list.style.webkitTransition = `transform ${time}ms cubic-bezier(0.19, 1, 0.22, 1)`;
} else {
this.$refs.list.style.webkitTransition = '';
}
this.$refs.list.style.webkitTransform = `translate3d(${translateX}px, 0, 0)`;
this.scrollDistance = translateX;
},
setMove(move, type, time) {
let updateMove = move + this.transformX;
let w = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
let offsetWidth = this.lineSpacing * this.listData.length;
if (type === 'end') {
if (updateMove > 0) {
updateMove = 0;
} else if (updateMove < -offsetWidth + w) {
if (-offsetWidth + w <= 0) {
updateMove = -offsetWidth + w;
} else {
updateMove = 0;
}
}
this.setTransform(updateMove, type, time)
} else {
let maxMove = -offsetWidth + w;
if (updateMove > 0 && updateMove > this.stretch) {
updateMove = this.stretc;
} else if (updateMove < maxMove - this.stretch) {
if (maxMove <= 0) {
updateMove = maxMove - this.stretch;
} else {
updateMove = updateMove < -this.stretch ? -this.stretch : updateMove;
}
}
this.setTransform(updateMove, null, null);
}
},
touchStart(event) {
event.preventDefault();
let changedTouches = event.changedTouches[0];
this.touchParams.startX = changedTouches.pageX;
this.touchParams.startTime = event.timestamp || Date.now();
this.transformX = this.scrollDistance;
},
touchMove(event) {
let changedTouches = event.changedTouches[0];
this.touchParams.lastX = changedTouches.pageX;
this.touchParams.lastTime = event.timestamp || Date.now();
let move = this.touchParams.lastX - this.touchParams.startX;
this.setMove(move);
},
touchEnd(event) {
event.preventDefault();
let changedTouches = event.changedTouches[0];
this.touchParams.lastX = changedTouches.pageX;
this.touchParams.lastTime = event.timestamp || Date.now();
let move = this.touchParams.lastX - this.touchParams.startX;
let moveTime = this.touchParams.lastTime - this.touchParams.startTime;
let w = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
let maxMove = -this.lineSpacing * this.listData.length + w;
// 释放跳转之类
if (this.isUnMore && move < 0 && (move + this.transformX) < maxMove - 50) {
//this.$emit('jump');
}
// 加载更多
if (!this.isLoading && !this.isUnMore && move < 0 && (move + this.transformX) < maxMove + 2 * w) {
this.$emit('loadMore');
}
if (moveTime <= 300) {
move = move * 2;
if (move < 0 && move + this.transformX < maxMove) {
move = maxMove - this.transformX;
}
moveTime = moveTime + 500;
this.setMove(move, 'end', moveTime);
} else {
this.setMove(move, 'end');
}
}
},
mounted() {
this.$nextTick(() => {
// 监听
this.$el.addEventListener('touchstart', this.touchStart);
this.$el.addEventListener('touchmove', this.touchMove);
this.$el.addEventListener('touchend', this.touchEnd);
});
},
beforeDestroy() {
// 移除监听
this.$el.removeEventListener('touchstart', this.touchStart);
this.$el.removeEventListener('touchmove', this.touchMove);
this.$el.removeEventListener('touchend', this.touchEnd);
clearTimeout(this.timer);
}
}
</script>
import Scroller from './scroller.vue';
import './scroller.scss';
Scroller.install = function(Vue) {
Vue.component(Scroller.name, Scroller);
};
export default Scroller
\ No newline at end of file
@import "../../styles/animation/rotate";
.nut-scroller{
display: flex;
height: 100%;
}
// 横向滚动
.nut-hor-scroll{
height: 100%;
width: 100%;
overflow: hidden;
.nut-hor-list{
height: 100%;
display: flex;
flex-direction: row;
box-orient: horizontal;
box-direction: normal;
}
.nut-hor-control{
height: 100%;
}
}
// 竖向滚动
.nut-vert-scroll{
flex: 1;
overflow: hidden;
.nut-vert-list{
width: 100%;
position: relative;
}
.nut-vert-pulldown{
position: absolute;
top: -50px;
width: 100%;
}
.nut-vert-loadmore, .nut-vert-pulldown, .nut-vert-unloadmore{
height: 50px;
text-align: center;
line-height: 50px;
font-size: 12px;
color: $text-color;
.nut-vert-pulldown-status, .nut-vert-loadmore-status{
height: 50px;
}
.nut-vert-loading{
height: 20px;
width: 20px;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADoAAAA6CAMAAADWZboaAAAAP1BMVEUAAAD0KA/xJhHxJA7yJA7xJA7xJA7xJA7xJA7wJA7xJA7xJA7xJA7xJA7xJA7xJA3xJA7xJA7xJA7xJA7xJA5JCyllAAAAFXRSTlMACxdEI3NalDdn3LyvLstPoveIgOl7Sm+EAAABiklEQVRIx9XS0W7kIAxAUWyDwckAIfD/37owUyndbbMJrtSq92UeoiMbGHMWRqKciSKamSw5v23b462d7T0HVIc76NrbGK8h+153vgozi9u39ZW7wOSfSYB3pyY3JqdV4ByiG86FTw6xrSmlx+mZ4757X8PJPo/Uo5OPe4/MaTwGy6dfOvz/VeAY7OGjrPvOcHH9PqXFf9i21krmMrcsi/vnhg55bfPfr1Irm1v5bqM5ElcF7lFYl2WF46DOOTA3w/TuuNBlMLeTvjIeQ8XcD9LS9mOoNRPl1t7GBufYTLW29jotP4dOjk2mhyJi5oKltfG2UYTMZL4VN6aL2FnKpTzGjwjMUiyl9b1F2Ey3lILjlvI8XUshY5lpnm6l8KBhnvpSxETmOE/3UpySjo6Fv5UiMykp5JyNsk5BSUPOqL2nnKP2sERklHWq3pgoaDf+nWPVfwsK6rcNIVilxW7xB6wNMVqtjXqLnVrQWbAj+IJFHcanRQAlxhH0xiJza7/owIrpMOBrsK4L9wckTA/GBoy0fQAAAABJRU5ErkJggg==) no-repeat 0 0;
background-size: 100%;
animation: rotation 2s linear infinite;
}
.nut-vert-loading-txt{
height: 50px;
padding-left: 5px;
}
.nut-vert-loading, .nut-vert-loading-txt{
display: inline-block;
vertical-align: middle;
}
}
}
\ No newline at end of file
<template>
<div class="nut-scroller">
<template v-if="type === 'vertical'">
<nut-vert-scroll
:stretch="stretch"
:is-un-more="isUnMore"
:is-loading="isLoading"
@loadMore="loadMore"
@pulldown="pulldown"
>
<slot name="list" slot="list"></slot>
</nut-vert-scroll>
</template>
<template v-else-if="type === 'horizontal'">
<nut-hor-scroll :list-data="listData"
:line-spacing="lineSpacing"
:stretch="stretch"
:is-un-more="isUnMore"
:is-loading="isLoading"
@loadMore="loadMore"
@jump="jump"
>
<slot name="list" slot="list"></slot>
<slot name="more" slot="more"></slot>
</nut-hor-scroll>
</template>
</div>
</template>
<script>
import nutVertScroll from "./vertical-scroll.vue";
import nutHorScroll from "./horizontal-scroll.vue";
export default {
name:'nut-scroller',
props: {
type: {
type: String,
default: 'horizontal' // horizontal vertical
},
listData: {
type: Array,
default: () => []
},
lineSpacing: {
type: Number,
default: 210
},
stretch: {
type: Number,
default: 200
},
isUnMore: {
type: Boolean,
default: false
},
isLoading: {
type: Boolean,
default: false
}
},
data() {
return {};
},
components: {
[nutVertScroll.name]: nutVertScroll,
[nutHorScroll.name]: nutHorScroll
},
methods: {
loadMore() {
this.$emit('loadMore');
},
jump() {
this.$emit('jump');
},
pulldown() {
this.$emit('pulldown');
}
}
}
</script>
\ No newline at end of file
<template>
<div class="nut-vert-scroll" ref="wrapper">
<div class="nut-vert-list" ref="list">
<div class="nut-vert-pulldown">
<div class="nut-vert-pulldown-txt" v-if="!isLoading">{{pulldownTxt}}</div>
<div class="nut-vert-pulldown-status" v-else>
<span class="nut-vert-loading"></span>
<span class="nut-vert-loading-txt">加载中...</span>
</div>
</div>
<slot name="list"></slot>
<div class="nut-vert-loadmore" v-if="!isUnMore && isShowLoadMore()">
<div class="nut-vert-load-txt" v-if="!isLoading">{{loadMoreTxt}}</div>
<div class="nut-vert-load-status" v-else>
<span class="nut-vert-loading"></span>
<span class="nut-vert-loading-txt">加载中...</span>
</div>
</div>
<div v-else-if="isUnMore" class="nut-vert-unloadmore" >
<div class="nut-vert-unloadmore-txt">{{unloadMoreTxt}}</div>
</div>
</div>
</div>
</template>
<script>
export default {
name:'nut-vert-scroll',
props: {
stretch: {
type: Number,
default: 50
},
isUnMore: {
type: Boolean,
default: false
},
isLoading: {
type: Boolean,
default: false
},
pulldownTxt: {
type: String,
default: '下拉刷新'
},
loadMoreTxt: {
type: String,
default: '上拉加载'
},
unloadMoreTxt: {
type: String,
default: '没有更多了'
}
},
watch: {
'isLoading': function(status) {
if (!status && this.realMove === 0) {
clearTimeout(this.timer);
this.setTransform(this.realMove, 'end', 0);
}
}
},
data() {
return {
touchParams: {
startY: 0,
endY: 0,
startTime: 0,
endTime: 0
},
translateY: 0,
scrollDistance: 0,
timer: null,
timerEmit: null,
realMove: 0
}
},
methods: {
isShowLoadMore() {
this.$nextTick(() => {
let wrapH = this.$refs.wrapper.offsetHeight;
let listH = this.$refs.list.offsetHeight;
if (wrapH <= listH) {
return true;
} else {
return false;
}
});
},
setTransform(translateY = 0, type, time = 500) {
if (type === 'end') {
this.$refs.list.style.webkitTransition = `transform ${time}ms cubic-bezier(0.19, 1, 0.22, 1)`;
} else {
this.$refs.list.style.webkitTransition = '';
}
this.$refs.list.style.webkitTransform = `translate3d(0, ${translateY}px, 0)`;
this.scrollDistance = translateY;
},
setMove(move, type, time) {
let updateMove = move + this.translateY;
let h = this.$refs.wrapper.offsetHeight;
let maxMove = -this.$refs.list.offsetHeight + h;
if (type === 'end') {
if (updateMove > 0) {
updateMove = 50;
this.realMove = 0;
if (!this.isLoading) {
clearTimeout(this.timerEmit);
this.timerEmit = setTimeout(() => {
this.$emit('pulldown');
}, time / 2);
}
} else if (updateMove < maxMove) {
if (maxMove <= 0) {
updateMove = maxMove;
} else {
updateMove = 0;
}
this.realMove = maxMove;
if (!this.isLoading && !this.isUnMore) {
clearTimeout(this.timerEmit);
this.timerEmit = setTimeout(() => {
this.$emit('loadMore');
}, time / 2);
}
}
if (updateMove == 50 && !this.isLoading) {
clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.setTransform(this.realMove, 'end', null);
}, 3000);
}
this.setTransform(updateMove, type, time)
} else {
// if (updateMove > 0 && updateMove > this.stretch) {
// updateMove = this.stretc;
// } else if (updateMove < maxMove - this.stretch) {
// updateMove = maxMove - this.stretch;
// }
this.setTransform(updateMove, null, null);
}
},
touchStart(event) {
event.preventDefault();
let changedTouches = event.changedTouches[0];
this.touchParams.startY = changedTouches.pageY;
this.touchParams.startTime = event.timestamp || Date.now();
this.translateY = this.scrollDistance;
},
touchMove(event) {
let changedTouches = event.changedTouches[0];
this.touchParams.lastY = changedTouches.pageY;
this.touchParams.lastTime = event.timestamp || Date.now();
let move = this.touchParams.lastY - this.touchParams.startY;
this.setMove(move);
},
touchEnd(event) {
event.preventDefault();
let changedTouches = event.changedTouches[0];
this.touchParams.lastY = changedTouches.pageY;
this.touchParams.lastTime = event.timestamp || Date.now();
let move = this.touchParams.lastY - this.touchParams.startY;
let moveTime = this.touchParams.lastTime - this.touchParams.startTime;
let h = this.$refs.wrapper.offsetHeight;
let maxMove = -this.$refs.list.offsetHeight + h;
if (moveTime <= 300) {
move = move * 2;
if (move < 0 && move < maxMove) {
move = maxMove;
}
moveTime = moveTime + 500;
this.setMove(move, 'end', moveTime);
} else {
this.setMove(move, 'end');
}
}
},
mounted() {
this.$nextTick(() => {
// 监听
this.$el.addEventListener('touchstart', this.touchStart);
this.$el.addEventListener('touchmove', this.touchMove);
this.$el.addEventListener('touchend', this.touchEnd);
});
},
beforeDestroy() {
// 移除监听
this.$el.removeEventListener('touchstart', this.touchStart);
this.$el.removeEventListener('touchmove', this.touchMove);
this.$el.removeEventListener('touchend', this.touchEnd);
clearTimeout(this.timer);
clearTimeout(this.timerEmit);
}
}
</script>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册