瀑布流加载功能实现 && 文档补全

This commit is contained in:
pangxie1991 2017-02-26 17:22:53 +08:00
parent 846f555861
commit 787ff255b8
8 changed files with 271 additions and 22 deletions

View File

@ -53,7 +53,8 @@ ComponentNames.forEach(name => {
// services
'Dialog',
'Toast',
'Indicator'
'Indicator',
'Waterfall'
].indexOf(componentName) === -1) {
installTemplate.push(render(ISNTALL_COMPONENT_TEMPLATE, {
name: componentName,

View File

@ -0,0 +1,84 @@
<script>
export default {
data() {
return {
list: [1, 2, 3, 4, 5],
loading: false,
finished: false
};
},
methods: {
loadMore() {
if (this.list.length >= 200) {
this.finished = true;
return;
}
this.loading = true;
setTimeout(() => {
let lastNumber = this.list[this.list.length - 1];
for (let i = 0; i < 5; i ++) {
lastNumber += 1;
this.list.push(lastNumber);
}
this.loading = false;
}, 2500);
},
loadMoreUpper() {
if (this.list[0] < 0) return;
this.list.unshift(-1);
}
},
computed: {
isWaterfallDisabled: function() {
return this.loading || this.finished;
}
}
};
</script>
<style lang="css">
.waterfall {
height: 300px;
overflow: scroll;
}
.waterfall-item {
line-height: 20px;
padding: 5px 0;
}
</style>
## Waterfall组件
### 基础用法
:::demo 样例代码
```html
<div class="waterfall">
<div
v-waterfall-lower="loadMore"
v-waterfall-upper="loadMoreUpper"
waterfall-disabled="isWaterfallDisabled"
waterfall-offset="400"
>
<div
class="waterfall-item"
v-for="item in list"
style="text-align: center;"
>
{{ item }}
</div>
<div v-if="loading" style="text-align: center;">
loading
</div>
</div>
</div>
```
:::
### API
| 参数 | 说明 | 类型 | 默认值 | 可选值 |
|-----------|-----------|-----------|-------------|-------------|
| waterfall-disabled | 是否禁止瀑布流触发 | Boolean | false | |
| waterfall-offset | 触发瀑布流加载的阈值 | Number | 300 | |

View File

@ -1,3 +1,3 @@
import SampleComponent from './src/main';
import Waterfall from './src/main.js';
export default SampleComponent;
export default Waterfall;

View File

@ -0,0 +1,94 @@
import Utils from './utils.js';
const CONTEXT = '@@Waterfall';
const OFFSET = 300;
// 绑定事件到元素上
// 读取基本的控制变量
function doBindEvent() {
this.scrollEventListener = Utils.debounce(handleScrollEvent.bind(this), 200);
this.scrollEventTarget = Utils.getScrollEventTarget(this.el);
var disabledExpr = this.el.getAttribute('waterfall-disabled');
var disabled = false;
if (disabledExpr) {
this.vm.$watch(disabledExpr, (value) => {
this.disabled = value;
});
disabled = Boolean(this.vm[disabledExpr]);
}
this.disabled = disabled;
var offset = this.el.getAttribute('waterfall-offset');
this.offset = Number(offset) || OFFSET;
this.scrollEventTarget.addEventListener('scroll', this.scrollEventListener);
this.scrollEventListener();
}
// 处理滚动函数
function handleScrollEvent() {
let element = this.el;
let scrollEventTarget = this.scrollEventTarget;
// 已被禁止的滚动处理
if (this.disabled) return;
let targetScrollTop = Utils.getScrollTop(scrollEventTarget);
let targetBottom = targetScrollTop + Utils.getVisibleHeight(scrollEventTarget);
// 判断是否到了底
let needLoadMoreToLower = false;
if (element === scrollEventTarget) {
needLoadMoreToLower = scrollEventTarget.scollHeight - targetBottom < this.offset;
} else {
let elementBottom = Utils.getElementTop(element) - Utils.getElementTop(scrollEventTarget) + Utils.getVisibleHeight(element);
needLoadMoreToLower = elementBottom - Utils.getVisibleHeight(scrollEventTarget) < this.offset;
}
if (needLoadMoreToLower) {
this.cb['lower'] && this.cb['lower']({ target: scrollEventTarget, top: targetScrollTop });
}
// 判断是否到了顶
let needLoadMoreToUpper = false;
if (element === scrollEventTarget) {
needLoadMoreToUpper = targetScrollTop < this.offset;
} else {
let elementTop = Utils.getElementTop(element) - Utils.getElementTop(scrollEventTarget);
needLoadMoreToUpper = elementTop + this.offset > 0;
}
if (needLoadMoreToUpper) {
this.cb['upper'] && this.cb['upper']({ target: scrollEventTarget, top: targetScrollTop });
}
}
export default function(type) {
return {
bind(el, binding, vnode) {
if (!el[CONTEXT]) {
el[CONTEXT] = {
el,
vm: vnode.context,
cb: {}
};
}
el[CONTEXT].cb[type] = binding.value;
vnode.context.$on('hook:mounted', function() {
if (Utils.isAttached(el)) {
doBindEvent.call(el[CONTEXT]);
}
});
},
update(el) {
el[CONTEXT].scrollEventListener();
},
unbind(el) {
const context = el[CONTEXT];
context.scrollEventTarget.removeEventListener('scroll', context.scrollEventListener);
}
};
};

View File

@ -0,0 +1,14 @@
import Waterfall from './directive.js';
import Vue from 'vue';
const install = function(Vue) {
Vue.directive('WaterfallLower', Waterfall('lower'));
Vue.directive('WaterfallUpper', Waterfall('upper'));
};
if (!Vue.prototype.$isServer) {
Vue.use(install);
}
Waterfall.install = install;
export default Waterfall;

View File

@ -1,18 +0,0 @@
<template>
<div>
<h2>author: {{ author }}</h2>
<div>Hello {{ name }}</div>
</div>
</template>
<script>
export default {
name: 'Sample',
props: ['author'],
data() {
return {
name: 'World'
};
}
};
</script>

View File

@ -0,0 +1,75 @@
export default {
debounce(func, wait, immediate) {
var timeout, args, context, timestamp, result;
return function() {
context = this;
args = arguments;
timestamp = new Date();
var later = function() {
var last = (new Date()) - timestamp;
if (last < wait) {
timeout = setTimeout(later, wait - last);
} else {
timeout = null;
result = func.apply(context, args);
}
};
if (!timeout) {
timeout = setTimeout(later, wait);
}
return result;
};
},
// 找到最近的触发滚动事件的元素
getScrollEventTarget(element) {
var currentNode = element;
// bugfix, see http://w3help.org/zh-cn/causes/SD9013 and http://stackoverflow.com/questions/17016740/onscroll-function-is-not-working-for-chrome
while (currentNode && currentNode.tagName !== 'HTML' && currentNode.tagName !== 'BODY' && currentNode.nodeType === 1) {
var overflowY = this.getComputedStyle(currentNode).overflowY;
if (overflowY === 'scroll' || overflowY === 'auto') {
return currentNode;
}
currentNode = currentNode.parentNode;
}
return window;
},
// 判断元素是否被加入到页面节点内
isAttached(element) {
var currentNode = element.parentNode;
while (currentNode) {
if (currentNode.tagName === 'HTML') {
return true;
}
if (currentNode.nodeType === 11) {
return false;
}
currentNode = currentNode.parentNode;
}
return false;
},
// 获取滚动高度
getScrollTop(element) {
return 'scrollTop' in element ? element.scrollTop : element.pageYOffset;
},
// 获取元素距离顶部高度
getElementTop(element) {
if (element === window) {
return this.getScrollTop(window);
}
return element.getBoundingClientRect().top + this.getScrollTop(window);
},
getVisibleHeight(element) {
if (element === window) {
return element.innerHeight;
}
return element.getBoundingClientRect().height;
},
getComputedStyle: document.defaultView.getComputedStyle.bind(document.defaultView)
};

View File

@ -26,7 +26,6 @@ const install = function(Vue) {
Vue.component(Popup.name, Popup);
Vue.component(Picker.name, Picker);
Vue.component(RadioGroup.name, RadioGroup);
Vue.component(Waterfall.name, Waterfall);
};
// auto install