diff --git a/build/bin/build-entry.js b/build/bin/build-entry.js
index d43620767..a3a9a1a35 100644
--- a/build/bin/build-entry.js
+++ b/build/bin/build-entry.js
@@ -51,7 +51,8 @@ ComponentNames.forEach(name => {
       // services
     'Dialog',
     'Toast',
-    'Indicator'
+    'Indicator',
+    'Waterfall'
   ].indexOf(componentName) === -1) {
     installTemplate.push(render(ISNTALL_COMPONENT_TEMPLATE, {
       name: componentName,
diff --git a/components.json b/components.json
index 5d694f816..3b1dc2789 100644
--- a/components.json
+++ b/components.json
@@ -10,6 +10,7 @@
   "dialog": "./packages/dialog/index.js",
   "picker": "./packages/picker/index.js",
   "radio-group": "./packages/radio-group/index.js",
+  "waterfall": "./packages/waterfall/index.js",
   "loading": "./packages/loading/index.js",
   "panel": "./packages/panel/index.js",
   "card": "./packages/card/index.js"
diff --git a/docs/examples-docs/waterfall.md b/docs/examples-docs/waterfall.md
new file mode 100644
index 000000000..49420db32
--- /dev/null
+++ b/docs/examples-docs/waterfall.md
@@ -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 |   |
+
diff --git a/packages/waterfall/CHANGELOG.md b/packages/waterfall/CHANGELOG.md
new file mode 100644
index 000000000..e88c472b3
--- /dev/null
+++ b/packages/waterfall/CHANGELOG.md
@@ -0,0 +1,8 @@
+## 0.0.2 (2017-01-20)
+
+* 改了bug A
+* 加了功能B
+
+## 0.0.1 (2017-01-10)
+
+* 第一版
diff --git a/packages/waterfall/README.md b/packages/waterfall/README.md
new file mode 100644
index 000000000..13efadb2c
--- /dev/null
+++ b/packages/waterfall/README.md
@@ -0,0 +1,26 @@
+# @youzan/waterfall
+
+!!! 请在此处填写你的文档最简单描述 !!!
+
+[![version][version-image]][download-url]
+[![download][download-image]][download-url]
+
+[version-image]: http://npm.qima-inc.com/badge/v/@youzan/<%= name %>.svg?style=flat-square
+[download-image]: http://npm.qima-inc.com/badge/d/@youzan/<%= name %>.svg?style=flat-square
+[download-url]: http://npm.qima-inc.com/package/@youzan/<%= name %>
+
+## Demo
+
+## Usage
+
+## API
+
+| 参数       | 说明      | 类型       | 默认值       | 可选值       |
+|-----------|-----------|-----------|-------------|-------------|
+| className | 自定义额外类名 | string  | ''          | ''          |
+
+
+
+
+## License
+[MIT](https://opensource.org/licenses/MIT)
diff --git a/packages/waterfall/index.js b/packages/waterfall/index.js
new file mode 100644
index 000000000..d315e6f96
--- /dev/null
+++ b/packages/waterfall/index.js
@@ -0,0 +1,3 @@
+import Waterfall from './src/main.js';
+
+export default Waterfall;
diff --git a/packages/waterfall/package.json b/packages/waterfall/package.json
new file mode 100644
index 000000000..ec57bc0d4
--- /dev/null
+++ b/packages/waterfall/package.json
@@ -0,0 +1,10 @@
+{
+  "name": "waterfall",
+  "version": "0.0.1",
+  "description": "瀑布流",
+  "main": "./lib/index.js",
+  "author": "pangxie1991",
+  "license": "MIT",
+  "devDependencies": {},
+  "dependencies": {}
+}
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/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 9dd314a94..12bab1ca2 100644
--- a/src/index.js
+++ b/src/index.js
@@ -9,6 +9,7 @@ import Popup from '../packages/popup/index.js';
 import Dialog from '../packages/dialog/index.js';
 import Picker from '../packages/picker/index.js';
 import RadioGroup from '../packages/radio-group/index.js';
+import Waterfall from '../packages/waterfall/index.js';
 import Loading from '../packages/loading/index.js';
 import Panel from '../packages/panel/index.js';
 import Card from '../packages/card/index.js';
@@ -50,6 +51,7 @@ module.exports = {
   Dialog,
   Picker,
   RadioGroup,
+  Waterfall,
   Loading,
   Panel,
   Card