diff --git a/build/bin/build-entry.js b/build/bin/build-entry.js
index d343a9195..fffd532e8 100644
--- a/build/bin/build-entry.js
+++ b/build/bin/build-entry.js
@@ -53,7 +53,8 @@ ComponentNames.forEach(name => {
// services
'Dialog',
'Toast',
- 'Indicator'
+ 'Indicator',
+ 'Waterfall'
].indexOf(componentName) === -1) {
installTemplate.push(render(ISNTALL_COMPONENT_TEMPLATE, {
name: componentName,
diff --git a/docs/examples/waterfall.md b/docs/examples/waterfall.md
index e69de29bb..49420db32 100644
--- a/docs/examples/waterfall.md
+++ b/docs/examples/waterfall.md
@@ -0,0 +1,84 @@
+
+
+
+## Waterfall组件
+
+### 基础用法
+
+:::demo 样例代码
+```html
+
+
+
+ {{ item }}
+
+
+ loading
+
+
+
+```
+:::
+
+### API
+
+| 参数 | 说明 | 类型 | 默认值 | 可选值 |
+|-----------|-----------|-----------|-------------|-------------|
+| waterfall-disabled | 是否禁止瀑布流触发 | Boolean | false | |
+| waterfall-offset | 触发瀑布流加载的阈值 | Number | 300 | |
+
diff --git a/packages/waterfall/index.js b/packages/waterfall/index.js
index 59bd195b2..d315e6f96 100644
--- a/packages/waterfall/index.js
+++ b/packages/waterfall/index.js
@@ -1,3 +1,3 @@
-import SampleComponent from './src/main';
+import Waterfall from './src/main.js';
-export default SampleComponent;
+export default Waterfall;
diff --git a/packages/waterfall/src/directive.js b/packages/waterfall/src/directive.js
new file mode 100644
index 000000000..48f342e63
--- /dev/null
+++ b/packages/waterfall/src/directive.js
@@ -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);
+ }
+ };
+};
diff --git a/packages/waterfall/src/main.js b/packages/waterfall/src/main.js
new file mode 100644
index 000000000..02665b9a1
--- /dev/null
+++ b/packages/waterfall/src/main.js
@@ -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;
diff --git a/packages/waterfall/src/main.vue b/packages/waterfall/src/main.vue
deleted file mode 100644
index 0d2c38ebd..000000000
--- a/packages/waterfall/src/main.vue
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
author: {{ author }}
-
Hello {{ name }}
-
-
-
-
diff --git a/packages/waterfall/src/utils.js b/packages/waterfall/src/utils.js
new file mode 100644
index 000000000..5f113ad53
--- /dev/null
+++ b/packages/waterfall/src/utils.js
@@ -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)
+};
diff --git a/src/index.js b/src/index.js
index 5915d777f..abbc3fe65 100644
--- a/src/index.js
+++ b/src/index.js
@@ -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