Browse Source

add Scroller

宋成林 7 years ago
parent
commit
ab188fdeab

BIN
src/assets/img/loading.png


+ 22 - 13
src/config.json

@@ -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"
     }
   ]
 }

+ 200 - 0
src/packages/scroller/demo.vue

@@ -0,0 +1,200 @@
+<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>

+ 0 - 0
src/packages/scroller/doc.md


+ 173 - 0
src/packages/scroller/horizontal-scroll.vue

@@ -0,0 +1,173 @@
+<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>
+

+ 8 - 0
src/packages/scroller/index.js

@@ -0,0 +1,8 @@
+import Scroller from './scroller.vue';
+import './scroller.scss';
+
+Scroller.install = function(Vue) {
+  Vue.component(Scroller.name, Scroller);
+};
+
+export default Scroller

File diff suppressed because it is too large
+ 62 - 0
src/packages/scroller/scroller.scss


+ 81 - 0
src/packages/scroller/scroller.vue

@@ -0,0 +1,81 @@
+<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>

+ 208 - 0
src/packages/scroller/vertical-scroll.vue

@@ -0,0 +1,208 @@
+<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>
+