diff --git a/src-next/loading/README.md b/src-next/loading/README.md
new file mode 100644
index 000000000..0f040966f
--- /dev/null
+++ b/src-next/loading/README.md
@@ -0,0 +1,60 @@
+# Loading
+
+### Install
+
+```js
+import Vue from 'vue';
+import { Loading } from 'vant';
+
+Vue.use(Loading);
+```
+
+## Usage
+
+### Type
+
+```html
+
+```
+
+### Color
+
+```html
+
+```
+
+### Size
+
+```html
+
+```
+
+### Text
+
+```html
+Loading...
+```
+
+### Vertical
+
+```html
+Loading...
+```
+
+## API
+
+### Props
+
+| Attribute | Description | Type | Default |
+| --- | --- | --- | --- |
+| color | Loading color | _string_ | `#c9c9c9` |
+| type | Can be set to `spinner` | _string_ | `circular` |
+| size | Icon size | _number \| string_ | `30px` |
+| text-size | Text font size | _number \| string_ | `14px` |
+| vertical | Whether to arrange icons and text content vertically | _boolean_ | `false` |
+
+### Slots
+
+| Name | Description |
+| ------- | ------------ |
+| default | Loading text |
diff --git a/src-next/loading/README.zh-CN.md b/src-next/loading/README.zh-CN.md
new file mode 100644
index 000000000..9531f5ddf
--- /dev/null
+++ b/src-next/loading/README.zh-CN.md
@@ -0,0 +1,70 @@
+# Loading 加载
+
+### 引入
+
+```js
+import Vue from 'vue';
+import { Loading } from 'vant';
+
+Vue.use(Loading);
+```
+
+## 代码演示
+
+### 加载类型
+
+通过`type`属性可以设置加载图标的类型,默认为`circular`,可选值为`spinner`
+
+```html
+
+```
+
+### 自定义颜色
+
+通过`color`属性设置加载图标的颜色
+
+```html
+
+```
+
+### 自定义大小
+
+通过`size`属性设置加载图标的大小,默认单位为`px`
+
+```html
+
+```
+
+### 加载文案
+
+可以使用默认插槽在图标的右侧插入加载文案
+
+```html
+加载中...
+```
+
+### 垂直排列
+
+设置`vertical`属性后,图标和文案会垂直排列
+
+```html
+加载中...
+```
+
+## API
+
+### Props
+
+| 参数 | 说明 | 类型 | 默认值 |
+| --------- | ---------------------------- | ------------------ | ---------- |
+| color | 颜色 | _string_ | `#c9c9c9` |
+| type | 类型,可选值为 `spinner` | _string_ | `circular` |
+| size | 加载图标大小,默认单位为`px` | _number \| string_ | `30px` |
+| text-size | 文字大小,默认单位为`px` | _number \| string_ | `14px` |
+| vertical | 是否垂直排列图标和文字内容 | _boolean_ | `false` |
+
+### Slots
+
+| 名称 | 说明 |
+| ------- | -------- |
+| default | 加载文案 |
diff --git a/src-next/loading/demo/index.vue b/src-next/loading/demo/index.vue
new file mode 100644
index 000000000..16cb624ab
--- /dev/null
+++ b/src-next/loading/demo/index.vue
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('loading') }}
+
+
+
+
+
+ {{ t('loading') }}
+
+
+
+
+
+
+
+
diff --git a/src-next/loading/index.js b/src-next/loading/index.js
new file mode 100644
index 000000000..445cfb383
--- /dev/null
+++ b/src-next/loading/index.js
@@ -0,0 +1,64 @@
+// Utils
+import { createNamespace, addUnit } from '../utils';
+
+const [createComponent, bem] = createNamespace('loading');
+
+const SpinIcon = [];
+for (let i = 0; i < 12; i++) {
+ SpinIcon.push();
+}
+
+const CircularIcon = (
+
+);
+
+export default createComponent({
+ props: {
+ color: String,
+ size: [Number, String],
+ vertical: Boolean,
+ textSize: [Number, String],
+ type: {
+ type: String,
+ default: 'circular',
+ },
+ },
+
+ methods: {
+ genLoadingText() {
+ if (this.$slots.default) {
+ const style = this.textSize && {
+ fontSize: addUnit(this.textSize),
+ };
+
+ return (
+
+ {this.$slots.default()}
+
+ );
+ }
+ },
+ },
+
+ render() {
+ const { color, size, type, vertical } = this;
+
+ const style = { color };
+ if (size) {
+ const iconSize = addUnit(size);
+ style.width = iconSize;
+ style.height = iconSize;
+ }
+
+ return (
+
+
+ {type === 'spinner' ? SpinIcon : CircularIcon}
+
+ {this.genLoadingText()}
+
+ );
+ },
+});
diff --git a/src-next/loading/index.less b/src-next/loading/index.less
new file mode 100644
index 000000000..ff2ed370a
--- /dev/null
+++ b/src-next/loading/index.less
@@ -0,0 +1,103 @@
+@import '../style/var';
+
+.van-loading {
+ position: relative;
+ color: @loading-spinner-color;
+ font-size: 0;
+ vertical-align: middle;
+
+ &__spinner {
+ position: relative;
+ display: inline-block;
+ width: @loading-spinner-size;
+ // compatible for 1.x, users may set width or height in root element
+ max-width: 100%;
+ height: @loading-spinner-size;
+ max-height: 100%;
+ vertical-align: middle;
+ animation: van-rotate @loading-spinner-animation-duration linear infinite;
+
+ &--spinner {
+ animation-timing-function: steps(12);
+
+ i {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+
+ &::before {
+ display: block;
+ width: 2px;
+ height: 25%;
+ margin: 0 auto;
+ background-color: currentColor;
+ border-radius: 40%;
+ content: ' ';
+ }
+ }
+ }
+
+ &--circular {
+ animation-duration: 2s;
+ }
+ }
+
+ &__circular {
+ display: block;
+ width: 100%;
+ height: 100%;
+
+ circle {
+ animation: van-circular 1.5s ease-in-out infinite;
+ stroke: currentColor;
+ stroke-width: 3;
+ stroke-linecap: round;
+ }
+ }
+
+ &__text {
+ display: inline-block;
+ margin-left: @padding-xs;
+ color: @loading-text-color;
+ font-size: @loading-text-font-size;
+ vertical-align: middle;
+ }
+
+ &--vertical {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+
+ .van-loading__text {
+ margin: @padding-xs 0 0;
+ }
+ }
+}
+
+@keyframes van-circular {
+ 0% {
+ stroke-dasharray: 1, 200;
+ stroke-dashoffset: 0;
+ }
+
+ 50% {
+ stroke-dasharray: 90, 150;
+ stroke-dashoffset: -40;
+ }
+
+ 100% {
+ stroke-dasharray: 90, 150;
+ stroke-dashoffset: -120;
+ }
+}
+
+.generate-spinner(@n, @i: 1) when (@i =< @n) {
+ .van-loading__spinner--spinner i:nth-of-type(@{i}) {
+ transform: rotate(@i * 30deg);
+ opacity: 1 - (0.75 / 12) * (@i - 1);
+ }
+ .generate-spinner(@n, (@i + 1));
+}
+.generate-spinner(12);
diff --git a/src-next/loading/test/__snapshots__/demo.spec.js.snap b/src-next/loading/test/__snapshots__/demo.spec.js.snap
new file mode 100644
index 000000000..424aa506c
--- /dev/null
+++ b/src-next/loading/test/__snapshots__/demo.spec.js.snap
@@ -0,0 +1,28 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders demo correctly 1`] = `
+
+`;
diff --git a/src-next/loading/test/__snapshots__/index.spec.js.snap b/src-next/loading/test/__snapshots__/index.spec.js.snap
new file mode 100644
index 000000000..925bda61e
--- /dev/null
+++ b/src-next/loading/test/__snapshots__/index.spec.js.snap
@@ -0,0 +1,5 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`size prop 1`] = `
`;
+
+exports[`text-size prop 1`] = `Text
`;
diff --git a/src-next/loading/test/demo.spec.js b/src-next/loading/test/demo.spec.js
new file mode 100644
index 000000000..5c70922b5
--- /dev/null
+++ b/src-next/loading/test/demo.spec.js
@@ -0,0 +1,4 @@
+import Demo from '../demo';
+import { snapshotDemo } from '../../../test/demo';
+
+snapshotDemo(Demo);
diff --git a/src-next/loading/test/index.spec.js b/src-next/loading/test/index.spec.js
new file mode 100644
index 000000000..5459775a1
--- /dev/null
+++ b/src-next/loading/test/index.spec.js
@@ -0,0 +1,25 @@
+import { mount } from '../../../test';
+import Loading from '..';
+
+test('size prop', () => {
+ const wrapper = mount(Loading, {
+ propsData: {
+ size: 20,
+ },
+ });
+
+ expect(wrapper).toMatchSnapshot();
+});
+
+test('text-size prop', () => {
+ const wrapper = mount(Loading, {
+ propsData: {
+ textSize: 20,
+ },
+ scopedSlots: {
+ default: () => 'Text',
+ },
+ });
+
+ expect(wrapper).toMatchSnapshot();
+});
diff --git a/vant.config.js b/vant.config.js
index 67ce5e06d..585ec7160 100644
--- a/vant.config.js
+++ b/vant.config.js
@@ -192,10 +192,10 @@ module.exports = {
// path: 'dropdown-menu',
// title: 'DropdownMenu 下拉菜单',
// },
- // {
- // path: 'loading',
- // title: 'Loading 加载',
- // },
+ {
+ path: 'loading',
+ title: 'Loading 加载',
+ },
// {
// path: 'notify',
// title: 'Notify 消息通知',
@@ -543,10 +543,10 @@ module.exports = {
// path: 'dropdown-menu',
// title: 'DropdownMenu',
// },
- // {
- // path: 'loading',
- // title: 'Loading',
- // },
+ {
+ path: 'loading',
+ title: 'Loading',
+ },
// {
// path: 'notify',
// title: 'Notify',