diff --git a/document/common/config/menu.json b/document/common/config/menu.json
index 015ace191..d7e0c04cb 100644
--- a/document/common/config/menu.json
+++ b/document/common/config/menu.json
@@ -63,7 +63,10 @@
"scroll": "Scroll",
"slide": "Slide",
"index-list": "IndexList",
- "swipe": "Swipe"
+ "swipe": "Swipe",
+ "sticky": "Sticky",
+ "scroll-nav-bar": "ScrollNavBar",
+ "scroll-nav": "ScrollNav"
}
}
}
@@ -142,7 +145,10 @@
"scroll": "Scroll",
"slide": "Slide",
"index-list": "IndexList",
- "swipe": "Swipe"
+ "swipe": "Swipe",
+ "sticky": "Sticky",
+ "scroll-nav-bar": "ScrollNavBar",
+ "scroll-nav": "ScrollNav"
}
}
}
diff --git a/document/components/docs/en-US/scroll-nav-bar.md b/document/components/docs/en-US/scroll-nav-bar.md
new file mode 100644
index 000000000..a2ec04b14
--- /dev/null
+++ b/document/components/docs/en-US/scroll-nav-bar.md
@@ -0,0 +1,132 @@
+## ScrollNavBar
+
+> New in 1.10.0+
+
+Scroll navigation bar component, like DiDi business navigation.
+
+### Example
+
+- Default horizontal
+
+ ```html
+
+ ```
+ ```js
+ export default {
+ data() {
+ return {
+ current: '快车',
+ labels: [
+ '快车',
+ '小巴',
+ '专车',
+ '顺风车',
+ '代驾',
+ '公交',
+ '自驾租车',
+ '豪华车',
+ '二手车',
+ '出租车'
+ ]
+ }
+ },
+ methods: {
+ changeHandler(cur) {
+ this.current = cur
+ }
+ }
+ }
+ ```
+
+ You can set the active item by `current` Prop. `labels` is the collection if all the item's keys. The `change` events will be triggered when active item changed.
+
+- Vertical
+
+ ```html
+
+
+ {{props.txt}}
+
+
+ ```
+ ```js
+ export default {
+ data() {
+ return {
+ current: '快车',
+ labels: [
+ '快车',
+ '小巴',
+ '专车',
+ '顺风车',
+ '代驾',
+ '公交',
+ '自驾租车',
+ '豪华车',
+ '二手车',
+ '出租车'
+ ],
+ txts: [
+ '1快车',
+ '2小巴',
+ '3专车',
+ '4顺风车',
+ '5代驾',
+ '6公交',
+ '7自驾租车',
+ '8豪华车',
+ '9二手车',
+ '10出租车'
+ ]
+ }
+ },
+ methods: {
+ changeHandler(cur) {
+ this.current = cur
+ }
+ }
+ }
+ ```
+ ```stylus
+ .side-container
+ height: 300px
+ margin-top: 20px
+ ```
+
+ If the `direction` Prop is `vertical` then the ScrollNavBar will be vertical style.
+
+ You can use `txts` Prop to defined the text of showcases, it's order is correspondence with `labels` Prop. The default `txts` value is `labels`.
+
+ You can also use default scoped slot to defined your own item content.
+
+### Props
+
+| Attribute | Description | Type | Accepted Values | Default |
+| - | - | - | - | - |
+| direction | direction, default horizontal | String | horizontal/vertical | horizontal |
+| labels | the collection if all the item's keys | Array | - | [] |
+| txts | the text of showcases, it's order is correspondence with `labels` | Array | - | default equals to `labels` Prop |
+| current | the key of the active item | String/Number | - | '' |
+
+### Slot
+
+| Name | Description | Scope Parameters |
+| - | - | - |
+| default | default content | txt: item's text
index: item's index
active: the key of active item
label: item's label |
+
+### Events
+
+| Event Name | Description | Parameters |
+| - | - | - |
+| change | triggers when active item changed | active - the key of active item |
+
+### Instance methods
+
+| Method name | Description |
+| - | - |
+| refresh | You can call this method when content updated |
diff --git a/document/components/docs/en-US/scroll-nav.md b/document/components/docs/en-US/scroll-nav.md
new file mode 100644
index 000000000..6c50b7da8
--- /dev/null
+++ b/document/components/docs/en-US/scroll-nav.md
@@ -0,0 +1,176 @@
+## ScrollNav
+
+> New in 1.10.0+
+
+ScrollNav component. You can use it to sticky your navigation bar and content.
+
+### Example
+
+- Basic usage - Default
+
+ ```html
+
+
+
+ -
+
+
![]()
+
{{food.name}}
+
+
+
+
+
+ ```
+ ```js
+ import goodsData from 'example/data/goods-list.json'
+
+ const goods = goodsData.goods
+
+ export default {
+ data() {
+ return {
+ data: goods
+ }
+ },
+ methods: {
+ changeHandler(label) {
+ console.log('changed to:', label)
+ }
+ }
+ }
+ ```
+
+ `goods`:
+ ```js
+ "goods": [
+ {
+ "name": "热销榜",
+ "type": -1,
+ "foods": [
+ {
+ "name": "皮蛋瘦肉粥",
+ // ...
+ "icon": "http://fuss10.elemecdn.com/c/cd/c12745ed8a5171e13b427dbc39401jpeg.jpeg?imageView2/1/w/114/h/114",
+ "image": "http://fuss10.elemecdn.com/c/cd/c12745ed8a5171e13b427dbc39401jpeg.jpeg?imageView2/1/w/750/h/750"
+ },
+ // ...
+ ]
+ },
+ // ...
+ ]
+ ```
+
+ `cube-scroll-nav` should contains `cube-scroll-nav-panel` which is the navigation target panel content.
+
+ `cube-scroll-nav-panel` required Prop: `label`(the key of panel).
+
+ The `change` event will be triggered when navigation active item changed.
+
+- Side Style
+
+ ```html
+
+
+
+
+ -
+
+
![]()
+
{{food.name}}
+
+
+
+
+
+ ```
+ ```js
+ import goodsData from 'example/data/goods-list.json'
+
+ const goods = goodsData.goods
+
+ export default {
+ components: {
+ CubePage
+ },
+ data() {
+ return {
+ data: goods,
+ current: goods[3].name
+ }
+ },
+ methods: {
+ changeHandler(label) {
+ console.log('changed to:', label)
+ },
+ stickyChangeHandler(current) {
+ console.log('sticky-change', current)
+ }
+ }
+ }
+ ```
+
+ If set `side-style` to `true` then the navigation bar will be at the side position.
+
+ `data` is the data source, it will be passed to `cube-scroll` component, if the `data` updated then `cube-scroll` will be auto refreshed.
+
+ `current` is the default navigation value(label of panel), if set this value then the target panel will be auto scrolled to the top of container.
+
+ The `sticky-change` event will be triggered when the navigation bar's sticky state changed. The parameter is one of `''`, `'cube-scroll-nav-bar'`.
+
+ You can use `prepend` slot to insert your own content before the main content.
+
+### Props
+
+#### CubeScrollNav
+
+| Attribute | Description | Type | Accepted Values | Default |
+| - | - | - | - | - |
+| data | optional, data source | Array | - | - |
+| sideStyle | If set to `true` then the navigation bar will be at the side position | Boolean | true/false | false |
+| current | the default navigation value(label of panel) | String/Number | - | '' |
+| speed | speed of navigating to target panel | Number | - | 300 |
+
+#### CubeScrollNavPanel
+
+| Attribute | Description | Type | Accepted Values | Default |
+| - | - | - | - | - |
+| label | required, the key of panel | String/Number | - | - |
+| title | the title of panel | String/Number | - | default equal to `label` Prop |
+
+### Slot
+
+| Name | Description | Scope Parameters |
+| - | - | - |
+| default | default panel content | - |
+| prepend | prepend content | - |
+| bar | navigation bar content, you should use `cube-scroll-nav-bar` component to define your own navigation bar content | labels: the collection of all panel labels
txts: the collection of all panel titles
current: current active navigation value(label of panel) |
+
+### Events
+
+| Event Name | Description | Parameters |
+| - | - | - |
+| change | triggers when navigation active item changed | active - active navigation value(label of panel) |
+| sticky-change | triggers when the navigation bar's sticky state changed | current - if navigation bar is fixed then this value will be 'cube-scroll-nav-bar', otherwise this value will be '' |
+
+### Instance methods
+
+| Method name | Description |
+| - | - |
+| refresh | You can call this method when content updated |
diff --git a/document/components/docs/en-US/sticky.md b/document/components/docs/en-US/sticky.md
new file mode 100644
index 000000000..16b16c5c5
--- /dev/null
+++ b/document/components/docs/en-US/sticky.md
@@ -0,0 +1,299 @@
+## Sticky
+
+> New in 1.10.0+
+
+Sticky component, the element will be sticky when the scroll position matched the target elements position.
+
+### Example
+
+- Default usage - Scroll
+
+ ```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ```
+ ```js
+ const _data = [
+ '😀 😁 😂 🤣 😃 😄 ',
+ '🙂 🤗 🤩 🤔 🤨 😐 ',
+ '👆🏻 scroll up/down 👇🏻 ',
+ '😔 😕 🙃 🤑 😲 ☹️ ',
+ '🐣 🐣 🐣 🐣 🐣 🐣 ',
+ '👆🏻 scroll up/down 👇🏻 ',
+ '🐥 🐥 🐥 🐥 🐥 🐥 ',
+ '🤓 🤓 🤓 🤓 🤓 🤓 ',
+ '👆🏻 scroll up/down 👇🏻 ',
+ '🦔 🦔 🦔 🦔 🦔 🦔 ',
+ '🙈 🙈 🙈 🙈 🙈 🙈 ',
+ '👆🏻 scroll up/down 👇🏻 ',
+ '🚖 🚖 🚖 🚖 🚖 🚖 ',
+ '✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 '
+ ]
+ export default {
+ data() {
+ return {
+ scrollEvents: ['scroll'],
+ scrollY: 0,
+ items: _data.concat(),
+ items2: _data.concat(),
+ items3: _data.concat()
+ }
+ },
+ methods: {
+ scrollHandler({ y }) {
+ this.scrollY = -y
+ }
+ }
+ }
+ ```
+
+ `cube-sticky` need to use with `cube-sticky-ele` component.
+
+ The `pos` Prop of `cube-sticky` component is the scroll runtime position value.
+
+ The `ele-key` Prop of `cube-sticky-ele` component is optional, it is used to define the key value of sticky ele. The default `ele-key` value is the current sticky ele's index value.
+
+ You can alse use `cube-sticky`'s `fixed` slot to define your own sticky HTML. If you do not use `fixed` slot then the default sticky content will be the active `cube-sticky-ele`'s content.
+
+- Default usage - Native Scroll
+
+ ```html
+
+
+
+ ```
+ ```js
+ const _data = [
+ '😀 😁 😂 🤣 😃 😄 ',
+ '🙂 🤗 🤩 🤔 🤨 😐 ',
+ '👆🏻 scroll up/down 👇🏻 ',
+ '😔 😕 🙃 🤑 😲 ☹️ ',
+ '🐣 🐣 🐣 🐣 🐣 🐣 ',
+ '👆🏻 scroll up/down 👇🏻 ',
+ '🐥 🐥 🐥 🐥 🐥 🐥 ',
+ '🤓 🤓 🤓 🤓 🤓 🤓 ',
+ '👆🏻 scroll up/down 👇🏻 ',
+ '🦔 🦔 🦔 🦔 🦔 🦔 ',
+ '🙈 🙈 🙈 🙈 🙈 🙈 ',
+ '👆🏻 scroll up/down 👇🏻 ',
+ '🚖 🚖 🚖 🚖 🚖 🚖 ',
+ '✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 '
+ ]
+
+ export default {
+ data() {
+ return {
+ scrollY: 0,
+ checkTop: false,
+ items: _data.concat(),
+ items2: _data.concat(),
+ items3: _data.concat()
+ }
+ },
+ methods: {
+ scrollHandler(e) {
+ this.scrollY = e.currentTarget.scrollTop
+ }
+ }
+ }
+ ```
+ ```stylus
+ .scroll-ele
+ height: 100%
+ overflow: auto
+ -webkit-overflow-scrolling: touch
+ background-color: #fff
+ ```
+
+ `check-top` Prop is used to control the checking of boundary conditions, it's default value is `true`, this means `cube-sticky-ele` element will be sticky when the top of the `cube-sticky-ele` reaches the top of `cube-sticky`. This case the `check-top` is `false` which means `cube-sticky-ele` element will be sticky when the bottom of the `cube-sticky-ele` reaches the top of `cube-sticky`.
+
+- Like WeChat
+
+ ```html
+
+
+
+
+
+
+
+
+
+
+
+
+ ```
+ ```js
+ const _data = [
+ '😀 😁 😂 🤣 😃 😄 ',
+ '🙂 🤗 🤩 🤔 🤨 😐 ',
+ '👆🏻 scroll up/down 👇🏻 ',
+ '😔 😕 🙃 🤑 😲 ☹️ ',
+ '🐣 🐣 🐣 🐣 🐣 🐣 ',
+ '👆🏻 scroll up/down 👇🏻 ',
+ '🐥 🐥 🐥 🐥 🐥 🐥 ',
+ '🤓 🤓 🤓 🤓 🤓 🤓 ',
+ '👆🏻 scroll up/down 👇🏻 ',
+ '🦔 🦔 🦔 🦔 🦔 🦔 ',
+ '🙈 🙈 🙈 🙈 🙈 🙈 ',
+ '👆🏻 scroll up/down 👇🏻 ',
+ '🚖 🚖 🚖 🚖 🚖 🚖 ',
+ '✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 '
+ ]
+
+ export default {
+ data() {
+ return {
+ scrollEvents: ['scroll'],
+ scrollY: 0,
+ checkTop: true,
+ items: _data.concat(),
+ items2: _data.concat(),
+ items3: _data.concat()
+ }
+ },
+ methods: {
+ scrollHandler({ y }) {
+ this.scrollY = -y
+ },
+ diffChange() {
+ let opacity = 0
+ if (height) {
+ opacity = diff / height
+ }
+ this.$refs.stickyHeader.style.opacity = opacity
+ }
+ }
+ }
+ ```
+ ```stylus
+ .sticky-fixed-show-enter, .sticky-fixed-show-leave-active
+ transform: translate(0, -100%)
+ .sticky-fixed-show-enter-active, .sticky-fixed-show-leave-active
+ transition: all .5s ease-in-out
+ ```
+
+ `fixed-show-ani` is used to set the `transition` name of the sticky element when it's fixed.
+
+ You can get current sticky ele's fixed diff value on `diff-change` event.
+
+### Props
+
+#### CubeSticky
+
+| Attribute | Description | Type | Accepted Values | Default |
+| - | - | - | - | - |
+| pos | required, scroll position | Number | - | - |
+| checkTop | whether to treat the top of sticky element as boundary, if this value is false means `cube-sticky-ele` will be sticky when the bottom of the `cube-sticky-ele` reaches the top of `cube-sticky` | Boolean | true/false | true |
+| fixedShowAni | the `transition` name of the sticky element | String | - | if checkTop is true this value will be '', otherwise this value will be 'cube-sticky-fixed-fade' |
+| offset | offset value, `pos+offset` will be the real pos value | Number | - | 0 |
+
+#### CubeStickyEle
+
+| Attribute | Description | Type | Accepted Values | Default |
+| - | - | - | - | - |
+| eleKey | the key of sticky element | Number/String | - | - |
+
+If `eleKey` is undefined, CubeSticky will get the index of CubeStickyEle as the element's key.
+
+### Slot
+
+| Name | Description | Scope Parameters |
+| - | - | - |
+| default | default content | - |
+| fixed | custom sticky content when the sticky element is fixed | current: key of the fixed sticky element
index: index of the sticky element(index is not reactive) |
+
+### Events
+
+| Event Name | Description | Parameters 1 | Parameters 2 |
+| - | - | - | - |
+| change | triggers when fixed sticky element changed | current - key of the fixed sticky element, if there is no fixed sticky element this value will be '' | index - index of sticky element, if there is no fixed sticky element this value will be -1 (index is not reactive) |
+| diff-change | triggers when sticky element scrolling diff value changed | diff - diff value | height: height of current sticky element |
+
+### Instance methods
+
+| Method name | Description |
+| - | - |
+| refresh | You can call this method when content updated |
diff --git a/document/components/docs/zh-CN/scroll-nav-bar.md b/document/components/docs/zh-CN/scroll-nav-bar.md
new file mode 100644
index 000000000..057358bc6
--- /dev/null
+++ b/document/components/docs/zh-CN/scroll-nav-bar.md
@@ -0,0 +1,130 @@
+## ScrollNavBar 组件
+
+> 1.10.0 新增
+
+滚动导航条组件,效果类似于滴滴打车业务线切换。
+
+### 示例
+
+- 横向,默认
+
+ ```html
+
+ ```
+ ```js
+ export default {
+ data() {
+ return {
+ current: '快车',
+ labels: [
+ '快车',
+ '小巴',
+ '专车',
+ '顺风车',
+ '代驾',
+ '公交',
+ '自驾租车',
+ '豪华车',
+ '二手车',
+ '出租车'
+ ]
+ }
+ },
+ methods: {
+ changeHandler(cur) {
+ this.current = cur
+ }
+ }
+ }
+ ```
+
+ 可以通过 `current` 指定 active 的 key 值,`labels` 则是所有的 key 值的集合;当 active 的值发生变化后,会触发 `change` 事件。
+
+- 竖向
+
+ ```html
+
+
+ {{props.txt}}
+
+
+ ```
+ ```js
+ export default {
+ data() {
+ return {
+ current: '快车',
+ labels: [
+ '快车',
+ '小巴',
+ '专车',
+ '顺风车',
+ '代驾',
+ '公交',
+ '自驾租车',
+ '豪华车',
+ '二手车',
+ '出租车'
+ ],
+ txts: [
+ '1快车',
+ '2小巴',
+ '3专车',
+ '4顺风车',
+ '5代驾',
+ '6公交',
+ '7自驾租车',
+ '8豪华车',
+ '9二手车',
+ '10出租车'
+ ]
+ }
+ },
+ methods: {
+ changeHandler(cur) {
+ this.current = cur
+ }
+ }
+ }
+ ```
+ ```stylus
+ .side-container
+ height: 300px
+ margin-top: 20px
+ ```
+
+ 这是一个竖向的示例,`direction` 设置为 `vertical` 代表是一个竖向导航条样式;这里还使用了 `txts` Prop 用于指定展示文案,顺序是和 `labels` 一一对应的,如果没传入 `txts`,则默认 `txts` 就等于 `labels`。
+
+ 该示例中还展示了默认作用域插槽的使用,`props.txt` 就是当前项的 txt 的值。
+
+### Props
+
+| 参数 | 说明 | 类型 | 可选值 | 默认值 |
+| - | - | - | - | - |
+| direction | 方向,默认横向,horizontal | String | horizontal/vertical | horizontal |
+| labels | 所有的 key 值集合 | Array | - | [] |
+| txts | 显示所有文案集合,和 labels 一一对应 | Array | - | 默认等于 labes |
+| current | 当前 active 的 key 值 | String/Number | - | '' |
+
+### 插槽
+
+| 名字 | 说明 | 作用域参数 |
+| - | - | - |
+| default | 默认内容 | txt: 当前项文案 txt 的值
index: 当前索引值
active: 当前 active 的 key 值
label: 当前项 label 值,即 key 值 |
+
+### 事件
+
+| 事件名 | 说明 | 参数 |
+| - | - | - |
+| change | 当 active 项发生改变的时候触发 | active - 当前 active 的 key 值 |
+
+### 实例方法
+
+| 方法名 | 说明 |
+| - | - |
+| refresh | 刷新,当内容发生变化时可用于刷新 scroll 以及重新适应位置 |
diff --git a/document/components/docs/zh-CN/scroll-nav.md b/document/components/docs/zh-CN/scroll-nav.md
new file mode 100644
index 000000000..c6a5d9910
--- /dev/null
+++ b/document/components/docs/zh-CN/scroll-nav.md
@@ -0,0 +1,176 @@
+## ScrollNav 组件
+
+> 1.10.0 新增
+
+滚动导航组件。
+
+### 示例
+
+- 基本使用 - Default
+
+ ```html
+
+
+
+ -
+
+
![]()
+
{{food.name}}
+
+
+
+
+
+ ```
+ ```js
+ import goodsData from 'example/data/goods-list.json'
+
+ const goods = goodsData.goods
+
+ export default {
+ data() {
+ return {
+ data: goods
+ }
+ },
+ methods: {
+ changeHandler(label) {
+ console.log('changed to:', label)
+ }
+ }
+ }
+ ```
+
+ `goods` 的结构类似于这样:
+ ```js
+ "goods": [
+ {
+ "name": "热销榜",
+ "type": -1,
+ "foods": [
+ {
+ "name": "皮蛋瘦肉粥",
+ // ...
+ "icon": "http://fuss10.elemecdn.com/c/cd/c12745ed8a5171e13b427dbc39401jpeg.jpeg?imageView2/1/w/114/h/114",
+ "image": "http://fuss10.elemecdn.com/c/cd/c12745ed8a5171e13b427dbc39401jpeg.jpeg?imageView2/1/w/750/h/750"
+ },
+ // ...
+ ]
+ },
+ // ...
+ ]
+ ```
+
+ `cube-scroll-nav` 需要和 `cube-scroll-nav-panel` 组件配合使用,`cube-scroll-nav-panel` 就是每一个需要导航定位到的面板组件。
+
+ `cube-scroll-nav-panel` 组件需要一个必须的 Prop:`label`,唯一标示 key 值。
+
+ 当导航 active 项发生变化的时候就会触发 `change` 事件。
+
+- 侧边式 - Side Style
+
+ ```html
+
+
+
+
+ -
+
+
![]()
+
{{food.name}}
+
+
+
+
+
+ ```
+ ```js
+ import goodsData from 'example/data/goods-list.json'
+
+ const goods = goodsData.goods
+
+ export default {
+ components: {
+ CubePage
+ },
+ data() {
+ return {
+ data: goods,
+ current: goods[3].name
+ }
+ },
+ methods: {
+ changeHandler(label) {
+ console.log('changed to:', label)
+ },
+ stickyChangeHandler(current) {
+ console.log('sticky-change', current)
+ }
+ }
+ }
+ ```
+
+ `side-style` 设置为 `true` 代表是一个侧边样式效果;
+
+ `data` 则是默认传入的数据,这个数据会被传入内部使用的 `cube-scroll` 组件,当 `data` 发生变化时,scroll 组件会自动刷新;
+
+ `current` 是导航默认值,如果有值则会默认滚动到相对应的面板 Panel 的位置。
+
+ `sticky-change` 就是当前导航条的吸附状态发生改变的时候触发,参数可能的值目前只有两个,一个是 `''` 一个是 `'cube-scroll-nav-bar'`。
+
+ 此外,这里还在 `cube-scroll-nav-panel` 之前插入了一些内容,利用 `prepend` 插槽实现。
+
+### Props
+
+#### CubeScrollNav
+
+| 参数 | 说明 | 类型 | 可选值 | 默认值 |
+| - | - | - | - | - |
+| data | 数据源,可选 | Array | - | - |
+| sideStyle | 是否是侧边样式 | Boolean | true/false | false |
+| current | 当前导航 active 项的 key 值 | String/Number | - | '' |
+| speed | 点击导航切换到指定位置的速度 | Number | - | 300 |
+
+#### CubeScrollNavPanel
+
+| 参数 | 说明 | 类型 | 可选值 | 默认值 |
+| - | - | - | - | - |
+| label | 必须,面板的唯一标示的值 | String/Number | - | - |
+| title | 面板标题内容 | String/Number | - | 默认等于 label |
+
+### 插槽
+
+| 名字 | 说明 | 作用域参数 |
+| - | - | - |
+| default | 默认面板内容 | - |
+| prepend | 前置内容,在导航之前 | - |
+| bar | 导航内容,如果需要自定义导航结构的话使用,里边放置 cube-scroll-nav-bar 组件 | labels: 所有面板的 label 值集合
txts: 所有面板的 title 值集合
current: 当前导航 active 项的 key 值 |
+
+### 事件
+
+| 事件名 | 说明 | 参数 |
+| - | - | - |
+| change | 当导航 active 项发生变化的时候 | active - 当前 active 的 key 值 |
+| sticky-change | 当前导航条的吸附状态发生改变的时候触发 | current - 如果未吸附,则值为 '' 否则为 'cube-scroll-nav-bar' |
+
+### 实例方法
+
+| 方法名 | 说明 |
+| - | - |
+| refresh | 刷新,当内容或高度发生变化时可用于重新计算整体内容、位置 |
diff --git a/document/components/docs/zh-CN/sticky.md b/document/components/docs/zh-CN/sticky.md
new file mode 100644
index 000000000..d24cdc423
--- /dev/null
+++ b/document/components/docs/zh-CN/sticky.md
@@ -0,0 +1,303 @@
+## Sticky 组件
+
+> 1.10.0 新增
+
+吸附组件,当滚动位置到达目标元素位置后,目标元素就会自动吸附。
+
+### 示例
+
+- 综合使用 - Scroll
+
+ ```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ```
+ ```js
+ const _data = [
+ '😀 😁 😂 🤣 😃 😄 ',
+ '🙂 🤗 🤩 🤔 🤨 😐 ',
+ '👆🏻 scroll up/down 👇🏻 ',
+ '😔 😕 🙃 🤑 😲 ☹️ ',
+ '🐣 🐣 🐣 🐣 🐣 🐣 ',
+ '👆🏻 scroll up/down 👇🏻 ',
+ '🐥 🐥 🐥 🐥 🐥 🐥 ',
+ '🤓 🤓 🤓 🤓 🤓 🤓 ',
+ '👆🏻 scroll up/down 👇🏻 ',
+ '🦔 🦔 🦔 🦔 🦔 🦔 ',
+ '🙈 🙈 🙈 🙈 🙈 🙈 ',
+ '👆🏻 scroll up/down 👇🏻 ',
+ '🚖 🚖 🚖 🚖 🚖 🚖 ',
+ '✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 '
+ ]
+ export default {
+ data() {
+ return {
+ scrollEvents: ['scroll'],
+ scrollY: 0,
+ items: _data.concat(),
+ items2: _data.concat(),
+ items3: _data.concat()
+ }
+ },
+ methods: {
+ scrollHandler({ y }) {
+ this.scrollY = -y
+ }
+ }
+ }
+ ```
+
+ `cube-sticky` 需要和 `cube-sticky-ele` 组件配合使用。
+
+ `cube-sticky` 组件依赖 `pos` Prop 来指定当前实时位置,由于 Scroll 组件中派发的 `y` 为负值,所以我们需要将其转换为滚动位置,直接取 `-y` 即可。
+
+ `cube-sticky-ele` 组件的 `ele-key` Prop 是可选的,用于指定当前吸附元素的 key 值,默认为当前组件的索引值,即在 `cube-sticky` 组件中的第几个 `cube-sticky-ele`。
+
+ 我们还可以利用 `cube-sticky` 组件的 `fixed` 插槽实现自定义吸附效果,如果没有插槽的话,内部默认会将吸附的 `cube-sticky-ele` 的内容显示出来。
+
+- 综合使用 - Native Scroll
+
+ 利用原生滚动实现效果。
+
+ ```html
+
+
+
+ ```
+ ```js
+ const _data = [
+ '😀 😁 😂 🤣 😃 😄 ',
+ '🙂 🤗 🤩 🤔 🤨 😐 ',
+ '👆🏻 scroll up/down 👇🏻 ',
+ '😔 😕 🙃 🤑 😲 ☹️ ',
+ '🐣 🐣 🐣 🐣 🐣 🐣 ',
+ '👆🏻 scroll up/down 👇🏻 ',
+ '🐥 🐥 🐥 🐥 🐥 🐥 ',
+ '🤓 🤓 🤓 🤓 🤓 🤓 ',
+ '👆🏻 scroll up/down 👇🏻 ',
+ '🦔 🦔 🦔 🦔 🦔 🦔 ',
+ '🙈 🙈 🙈 🙈 🙈 🙈 ',
+ '👆🏻 scroll up/down 👇🏻 ',
+ '🚖 🚖 🚖 🚖 🚖 🚖 ',
+ '✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 '
+ ]
+
+ export default {
+ data() {
+ return {
+ scrollY: 0,
+ checkTop: false,
+ items: _data.concat(),
+ items2: _data.concat(),
+ items3: _data.concat()
+ }
+ },
+ methods: {
+ scrollHandler(e) {
+ this.scrollY = e.currentTarget.scrollTop
+ }
+ }
+ }
+ ```
+ ```stylus
+ .scroll-ele
+ height: 100%
+ overflow: auto
+ -webkit-overflow-scrolling: touch
+ background-color: #fff
+ ```
+
+ 这个示例展示的就是利用原生滚动实现吸附效果的。
+
+ `check-top` Prop 则是用来控制检查条件的,默认为 `true`,意味着当 `cube-sticky-ele` 元素的顶部达到 `cube-sticky` 的顶部的时候就会被吸附;这个例子中我们设置为了 `false`,意味着当 `cube-sticky-ele` 的底部达到 `cube-sticky` 的顶部的时候会被吸附。
+
+- 模拟微信效果 - WeChat
+
+ ```html
+
+
+
+
+
+
+
+
+
+
+
+
+ ```
+ ```js
+ const _data = [
+ '😀 😁 😂 🤣 😃 😄 ',
+ '🙂 🤗 🤩 🤔 🤨 😐 ',
+ '👆🏻 scroll up/down 👇🏻 ',
+ '😔 😕 🙃 🤑 😲 ☹️ ',
+ '🐣 🐣 🐣 🐣 🐣 🐣 ',
+ '👆🏻 scroll up/down 👇🏻 ',
+ '🐥 🐥 🐥 🐥 🐥 🐥 ',
+ '🤓 🤓 🤓 🤓 🤓 🤓 ',
+ '👆🏻 scroll up/down 👇🏻 ',
+ '🦔 🦔 🦔 🦔 🦔 🦔 ',
+ '🙈 🙈 🙈 🙈 🙈 🙈 ',
+ '👆🏻 scroll up/down 👇🏻 ',
+ '🚖 🚖 🚖 🚖 🚖 🚖 ',
+ '✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 '
+ ]
+
+ export default {
+ data() {
+ return {
+ scrollEvents: ['scroll'],
+ scrollY: 0,
+ checkTop: true,
+ items: _data.concat(),
+ items2: _data.concat(),
+ items3: _data.concat()
+ }
+ },
+ methods: {
+ scrollHandler({ y }) {
+ this.scrollY = -y
+ },
+ diffChange() {
+ let opacity = 0
+ if (height) {
+ opacity = diff / height
+ }
+ this.$refs.stickyHeader.style.opacity = opacity
+ }
+ }
+ }
+ ```
+ ```stylus
+ .sticky-fixed-show-enter, .sticky-fixed-show-leave-active
+ transform: translate(0, -100%)
+ .sticky-fixed-show-enter-active, .sticky-fixed-show-leave-active
+ transition: all .5s ease-in-out
+ ```
+
+ 可以通过 `fixed-show-ani` 指定当元素吸附时出现的 `transition` 名字,我们这里指定 `sticky-fixed-show`,所以相对应的我们在样式中加了对应的动画控制效果。
+
+ 同时可以通过 `diff-change` 事件得到当前 sticky 元素滚动的差值,一般我们可以和当前 sticky 元素的高做除法得到相对百分比,可以精细控制出现的具体细节。
+
+### Props
+
+#### CubeSticky
+
+| 参数 | 说明 | 类型 | 可选值 | 默认值 |
+| - | - | - | - | - |
+| pos | 必须,滚动位置 | Number | - | - |
+| checkTop | 是否检测顶部位置,如果为 false,则检查 sticky-ele 元素的底部边界 | Boolean | true/false | true |
+| fixedShowAni | 元素吸附时指定 transition 的 name 值 | String | - | 如果 checkTop 为 true,则为 '',如果为 false,则为 'cube-sticky-fixed-fade' |
+| offset | 偏移值,传入的 pos 的值会加上这个值修正 | Number | - | 0 |
+
+#### CubeStickyEle
+
+| 参数 | 说明 | 类型 | 可选值 | 默认值 |
+| - | - | - | - | - |
+| eleKey | 吸顶元素的 key 值 | Number/String | - | - |
+
+当 `eleKey` 不存在时,CubeSticky 组件会获取当前 CubeStickyEle 组件的次序索引作为吸附元素的标示。
+
+### 插槽
+
+| 名字 | 说明 | 作用域参数 |
+| - | - | - |
+| default | 默认内容 | - |
+| fixed | 元素吸附时内容如何展示 | current: 当前吸附元素的 key 值
index: 吸附元素索引值(非响应式的) |
+
+### 事件
+
+| 事件名 | 说明 | 参数1 | 参数2 |
+| - | - | - | - |
+| change | 吸附的元素发生改变时触发 | current - 吸附的元素的 key 值,如果没有的话,则为 '' | index - 吸附元素的索引值,没有的话,则为 -1(非响应式的) |
+| diff-change | sticky 元素滚动的差值改变时触发 | diff - 差值,最小 0 | height: 当前 sticky 元素的高度 |
+
+### 实例方法
+
+| 方法名 | 说明 |
+| - | - |
+| refresh | 刷新,当内容发生变化时可用于重新计算高度和位置 |
diff --git a/example/App.vue b/example/App.vue
index be2086be4..b21ffc3f7 100644
--- a/example/App.vue
+++ b/example/App.vue
@@ -174,6 +174,18 @@
{
path: '/swipe',
text: 'Swipe'
+ },
+ {
+ path: '/sticky',
+ text: 'Sticky'
+ },
+ {
+ path: '/scroll-nav-bar',
+ text: 'ScrollNavBar'
+ },
+ {
+ path: '/scroll-nav',
+ text: 'ScrollNav'
}
]
}
diff --git a/example/data/goods-list.json b/example/data/goods-list.json
index ee8d7715c..d834c65c4 100644
--- a/example/data/goods-list.json
+++ b/example/data/goods-list.json
@@ -1376,4 +1376,4 @@
"recommend": []
}
]
-}
\ No newline at end of file
+}
diff --git a/example/pages/scroll-nav-bar.vue b/example/pages/scroll-nav-bar.vue
new file mode 100644
index 000000000..158651966
--- /dev/null
+++ b/example/pages/scroll-nav-bar.vue
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+ {{props.txt}}
+
+
+
+
+
+
+
+
+
diff --git a/example/pages/scroll-nav/default.vue b/example/pages/scroll-nav/default.vue
new file mode 100644
index 000000000..df845a15b
--- /dev/null
+++ b/example/pages/scroll-nav/default.vue
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+ -
+
+
![]()
+
{{food.name}}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/pages/scroll-nav/index.vue b/example/pages/scroll-nav/index.vue
new file mode 100644
index 000000000..f4aca2aae
--- /dev/null
+++ b/example/pages/scroll-nav/index.vue
@@ -0,0 +1,33 @@
+
+
+
+
+ Default
+ Side Style
+
+
+
+
+
+
+
+
+
diff --git a/example/pages/scroll-nav/side.vue b/example/pages/scroll-nav/side.vue
new file mode 100644
index 000000000..c52aa9a5c
--- /dev/null
+++ b/example/pages/scroll-nav/side.vue
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+ -
+
+
![]()
+
{{food.name}}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/pages/sticky/index.vue b/example/pages/sticky/index.vue
new file mode 100644
index 000000000..0f32e3b36
--- /dev/null
+++ b/example/pages/sticky/index.vue
@@ -0,0 +1,34 @@
+
+
+
+
+ Scroll
+ Native Scroll
+ WeChat
+
+
+
+
+
+
+
+
+
diff --git a/example/pages/sticky/native.vue b/example/pages/sticky/native.vue
new file mode 100644
index 000000000..6fa31fa05
--- /dev/null
+++ b/example/pages/sticky/native.vue
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/pages/sticky/scroll.vue b/example/pages/sticky/scroll.vue
new file mode 100644
index 000000000..ce6e32e77
--- /dev/null
+++ b/example/pages/sticky/scroll.vue
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/pages/sticky/wechat.vue b/example/pages/sticky/wechat.vue
new file mode 100644
index 000000000..d936295c2
--- /dev/null
+++ b/example/pages/sticky/wechat.vue
@@ -0,0 +1,119 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/router/routes.js b/example/router/routes.js
index 7b12b274a..87ecd456c 100644
--- a/example/router/routes.js
+++ b/example/router/routes.js
@@ -49,6 +49,14 @@ import Slide from '../pages/slide/index.vue'
import SlideVertical from '../pages/slide/vertical.vue'
import SlideHorizontal from '../pages/slide/horizontal.vue'
import Toolbar from '../pages/toolbar.vue'
+import Sticky from '../pages/sticky/index.vue'
+import StickyScroll from '../pages/sticky/scroll.vue'
+import StickyNative from '../pages/sticky/native.vue'
+import StickyWechat from '../pages/sticky/wechat.vue'
+import ScrollNavBar from '../pages/scroll-nav-bar.vue'
+import ScrollNav from '../pages/scroll-nav/index.vue'
+import ScrollNavDefault from '../pages/scroll-nav/default.vue'
+import ScrollNavSide from '../pages/scroll-nav/side.vue'
import ImagePreview from '../pages/image-preview.vue'
import TabBarIndex from '../pages/tab-bar/index.vue'
import TabBar from '../pages/tab-bar/tab-bar.vue'
@@ -284,6 +292,42 @@ const routes = [
path: '/toolbar',
component: Toolbar
},
+ {
+ path: '/sticky',
+ component: Sticky,
+ children: [
+ {
+ path: 'scroll',
+ component: StickyScroll
+ },
+ {
+ path: 'native',
+ component: StickyNative
+ },
+ {
+ path: 'wechat',
+ component: StickyWechat
+ }
+ ]
+ },
+ {
+ path: '/scroll-nav-bar',
+ component: ScrollNavBar
+ },
+ {
+ path: '/scroll-nav',
+ component: ScrollNav,
+ children: [
+ {
+ path: 'default',
+ component: ScrollNavDefault
+ },
+ {
+ path: 'side',
+ component: ScrollNavSide
+ }
+ ]
+ },
{
path: '/tab-bar',
component: TabBarIndex,
diff --git a/package-lock.json b/package-lock.json
index d3983118c..3a82f17b6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1613,9 +1613,9 @@
}
},
"better-scroll": {
- "version": "1.12.0",
- "resolved": "https://registry.npmjs.org/better-scroll/-/better-scroll-1.12.0.tgz",
- "integrity": "sha512-GYOith45ic5NIV1+hOAFVlx3M0PcmsZtt7Vj0MLi+wgsVTGxoP38h3pRb5DQzeho4L5Gz+E1xZG5lj/tEhWLMA==",
+ "version": "1.12.4",
+ "resolved": "https://registry.npmjs.org/better-scroll/-/better-scroll-1.12.4.tgz",
+ "integrity": "sha512-3C7iYCnPCBe9aR1EufpU0oyjQvogmZawIm1REZco1LXVNVnGhn26RJR4av73qcEFhD9HEFLOhYkAA0oREsGSRQ==",
"requires": {
"babel-runtime": "6.26.0"
}
diff --git a/package.json b/package.json
index ca3a8412a..a0c2e1ea8 100644
--- a/package.json
+++ b/package.json
@@ -56,7 +56,7 @@
],
"license": "Apache",
"dependencies": {
- "better-scroll": "^1.12.0"
+ "better-scroll": "^1.12.4"
},
"peerDependencies": {
"vue": "^2.5.13"
diff --git a/src/common/mixins/parent.js b/src/common/mixins/parent.js
new file mode 100644
index 000000000..bee9621ec
--- /dev/null
+++ b/src/common/mixins/parent.js
@@ -0,0 +1,22 @@
+export default function parentMixinCreator (relationKey, targetKey = 'relationParent') {
+ return {
+ created() {
+ this[targetKey] = this._getTargetParent()
+ },
+ destroyed() {
+ this[targetKey] = null
+ },
+ methods: {
+ _getTargetParent() {
+ let p = this.$parent
+ while (p) {
+ if (p[relationKey]) {
+ return p
+ }
+ p = p.$parent
+ }
+ return null
+ }
+ }
+ }
+}
diff --git a/src/common/stylus/theme/default.styl b/src/common/stylus/theme/default.styl
index fd925aa95..c5ec0abe8 100644
--- a/src/common/stylus/theme/default.styl
+++ b/src/common/stylus/theme/default.styl
@@ -207,6 +207,11 @@ $drawer-title-bgc := $color-white
$drawer-panel-bgc := $color-white
$drawer-item-active-bgc := $color-light-grey-opacity
+// scroll-nav
+$scroll-nav-bgc := $color-white
+$scroll-nav-color := $color-grey
+$scroll-nav-active-color := $color-orange
+
// image-preview
$image-preview-counter-color := $color-white
diff --git a/src/components/scroll-nav-bar/scroll-nav-bar.vue b/src/components/scroll-nav-bar/scroll-nav-bar.vue
new file mode 100644
index 000000000..59d9a3953
--- /dev/null
+++ b/src/components/scroll-nav-bar/scroll-nav-bar.vue
@@ -0,0 +1,169 @@
+
+
+
+
+
+
+
diff --git a/src/components/scroll-nav/scroll-nav-panel.vue b/src/components/scroll-nav/scroll-nav-panel.vue
new file mode 100644
index 000000000..e54ba4a65
--- /dev/null
+++ b/src/components/scroll-nav/scroll-nav-panel.vue
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/scroll-nav/scroll-nav.vue b/src/components/scroll-nav/scroll-nav.vue
new file mode 100644
index 000000000..bc9bd5111
--- /dev/null
+++ b/src/components/scroll-nav/scroll-nav.vue
@@ -0,0 +1,234 @@
+
+
+
+
+
+
+
diff --git a/src/components/scroll/scroll.vue b/src/components/scroll/scroll.vue
index dae18089d..66e1c2005 100644
--- a/src/components/scroll/scroll.vue
+++ b/src/components/scroll/scroll.vue
@@ -390,6 +390,9 @@
height: 100%
overflow: hidden
+ .cube-scroll-list-wrapper
+ overflow: hidden
+
.cube-pulldown-wrapper
position: absolute
width: 100%
diff --git a/src/components/sticky/sticky-ele.vue b/src/components/sticky/sticky-ele.vue
new file mode 100644
index 000000000..db57ad960
--- /dev/null
+++ b/src/components/sticky/sticky-ele.vue
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
diff --git a/src/components/sticky/sticky.vue b/src/components/sticky/sticky.vue
new file mode 100644
index 000000000..c97eef7d9
--- /dev/null
+++ b/src/components/sticky/sticky.vue
@@ -0,0 +1,204 @@
+
+
+
+
+
+
+
diff --git a/src/index.js b/src/index.js
index 63dd5c871..3fd77eaaa 100644
--- a/src/index.js
+++ b/src/index.js
@@ -36,6 +36,9 @@ import {
Slide,
IndexList,
Swipe,
+ Sticky,
+ ScrollNav,
+ ScrollNavBar,
// module
BetterScroll,
createAPI,
@@ -82,7 +85,10 @@ const components = [
Scroll,
Slide,
IndexList,
- Swipe
+ Swipe,
+ Sticky,
+ ScrollNav,
+ ScrollNavBar
]
function install(Vue) {
diff --git a/src/module.js b/src/module.js
index 2c22470af..64d153c16 100644
--- a/src/module.js
+++ b/src/module.js
@@ -39,6 +39,9 @@ import Scroll from './modules/scroll'
import Slide from './modules/slide'
import IndexList from './modules/index-list'
import Swipe from './modules/swipe'
+import Sticky from './modules/sticky'
+import ScrollNav from './modules/scroll-nav'
+import ScrollNavBar from './modules/scroll-nav-bar'
// module
import BetterScroll from './modules/better-scroll'
@@ -51,6 +54,8 @@ const Radio = RadioGroup.Radio
const SwipeItem = Swipe.Item
const DrawerPanel = Drawer.Panel
const DrawerItem = Drawer.Item
+const StickyEle = Sticky.Ele
+const ScrollNavPanel = ScrollNav.Panel
const Tab = TabBar.Tab
const TabPanel = TabPanels.Panel
@@ -102,6 +107,11 @@ export {
IndexList,
SwipeItem,
Swipe,
+ StickyEle,
+ Sticky,
+ ScrollNavPanel,
+ ScrollNav,
+ ScrollNavBar,
// module
BetterScroll,
createAPI
diff --git a/src/modules/scroll-nav-bar/index.js b/src/modules/scroll-nav-bar/index.js
new file mode 100644
index 000000000..ceb8a83cd
--- /dev/null
+++ b/src/modules/scroll-nav-bar/index.js
@@ -0,0 +1,7 @@
+import ScrollNavBar from '../../components/scroll-nav-bar/scroll-nav-bar.vue'
+
+ScrollNavBar.install = function (Vue) {
+ Vue.component(ScrollNavBar.name, ScrollNavBar)
+}
+
+export default ScrollNavBar
diff --git a/src/modules/scroll-nav/index.js b/src/modules/scroll-nav/index.js
new file mode 100644
index 000000000..e02bfaf46
--- /dev/null
+++ b/src/modules/scroll-nav/index.js
@@ -0,0 +1,14 @@
+import ScrollNav from '../../components/scroll-nav/scroll-nav.vue'
+import ScrollNavPanel from '../../components/scroll-nav/scroll-nav-panel.vue'
+import ScrollNavBar from '../../components/scroll-nav-bar/scroll-nav-bar.vue'
+
+ScrollNav.install = function (Vue) {
+ Vue.component(ScrollNav.name, ScrollNav)
+ Vue.component(ScrollNavPanel.name, ScrollNavPanel)
+ Vue.component(ScrollNavBar.name, ScrollNavBar)
+}
+
+ScrollNav.Panel = ScrollNavPanel
+ScrollNav.Bar = ScrollNavBar
+
+export default ScrollNav
diff --git a/src/modules/sticky/index.js b/src/modules/sticky/index.js
new file mode 100644
index 000000000..1a053e8c4
--- /dev/null
+++ b/src/modules/sticky/index.js
@@ -0,0 +1,11 @@
+import Sticky from '../../components/sticky/sticky.vue'
+import StickyEle from '../../components/sticky/sticky-ele.vue'
+
+Sticky.install = function (Vue) {
+ Vue.component(Sticky.name, Sticky)
+ Vue.component(StickyEle.name, StickyEle)
+}
+
+Sticky.Ele = Sticky
+
+export default Sticky
diff --git a/test/unit/specs/scroll-nav-bar.spec.js b/test/unit/specs/scroll-nav-bar.spec.js
new file mode 100644
index 000000000..65cf71b64
--- /dev/null
+++ b/test/unit/specs/scroll-nav-bar.spec.js
@@ -0,0 +1,114 @@
+import Vue from 'vue2'
+import ScrollNavBar from '@/modules/scroll-nav-bar'
+import instantiateComponent from '@/common/helpers/instantiate-component'
+import { dispatchTap } from '../utils/event'
+
+const current = '快车'
+const labels = [
+ '快车',
+ '小巴',
+ '专车',
+ '顺风车',
+ '代驾',
+ '公交',
+ '自驾租车',
+ '豪华车',
+ '二手车',
+ '出租车'
+]
+
+describe('ScrollNavBar', () => {
+ let vm
+
+ afterEach(() => {
+ if (vm) {
+ vm.$parent.destroy()
+ vm = null
+ }
+ })
+
+ it('use', () => {
+ Vue.use(ScrollNavBar)
+ expect(Vue.component(ScrollNavBar.name))
+ .to.be.a('function')
+ })
+
+ it('should render correct contents', (done) => {
+ vm = createScrollNavBar()
+ setTimeout(() => {
+ expect(vm.$el.className)
+ .to.equal('cube-scroll-nav-bar cube-scroll-nav-bar_horizontal')
+ const items = vm.$el.querySelectorAll('.cube-scroll-nav-bar-item')
+ expect(items.length)
+ .to.equal(10)
+ expect(items[0].className)
+ .to.include('cube-scroll-nav-bar-item_active')
+ vm.$updateProps({
+ current: '二手车'
+ })
+ setTimeout(() => {
+ expect(vm.$el.querySelector('.cube-scroll-nav-bar-item_active').textContent.trim())
+ .equal('二手车')
+ // click one
+ dispatchTap(vm.$el.querySelectorAll('.cube-scroll-nav-bar-item')[2])
+ setTimeout(() => {
+ expect(vm.$el.querySelector('.cube-scroll-nav-bar-item_active').textContent.trim())
+ .equal('专车')
+ done()
+ })
+ })
+ }, 50)
+ })
+
+ it('should render correct contents - vertical', (done) => {
+ vm = createScrollNavBar({
+ direction: 'vertical',
+ current: current,
+ labels: labels
+ })
+ setTimeout(() => {
+ vm.refresh()
+ expect(vm.$el.className)
+ .to.equal('cube-scroll-nav-bar cube-scroll-nav-bar_vertical')
+ const items = vm.$el.querySelectorAll('.cube-scroll-nav-bar-item')
+ expect(items.length)
+ .to.equal(10)
+ expect(items[0].className)
+ .to.include('cube-scroll-nav-bar-item_active')
+ done()
+ }, 50)
+ })
+
+ it('should trigger events', (done) => {
+ const changeHandler = sinon.spy()
+
+ vm = createScrollNavBar({
+ current: current,
+ labels: labels
+ }, {
+ change: changeHandler
+ })
+ setTimeout(() => {
+ dispatchTap(vm.$el.querySelectorAll('.cube-scroll-nav-bar-item')[0])
+ setTimeout(() => {
+ expect(changeHandler)
+ .not.to.be.called
+ dispatchTap(vm.$el.querySelectorAll('.cube-scroll-nav-bar-item')[2])
+ setTimeout(() => {
+ expect(changeHandler)
+ .to.be.calledOnce
+ expect(changeHandler)
+ .to.be.calledWith('专车')
+ done()
+ })
+ })
+ }, 50)
+ })
+
+ function createScrollNavBar(props = { current: current, labels: labels }, events = {}) {
+ return instantiateComponent(Vue, ScrollNavBar, {
+ props,
+ on: events
+ })
+ }
+})
diff --git a/test/unit/specs/scroll-nav.spec.js b/test/unit/specs/scroll-nav.spec.js
new file mode 100644
index 000000000..4df593c9f
--- /dev/null
+++ b/test/unit/specs/scroll-nav.spec.js
@@ -0,0 +1,191 @@
+import Vue from 'vue2'
+import ScrollNav from '@/modules/scroll-nav'
+import createVue from '../utils/create-vue'
+import { dispatchSwipe, dispatchTap } from '../utils/event'
+import { findIndex } from '@/common/helpers/util'
+import cityData from '../fake/index-list.json'
+
+describe('ScrollNav', () => {
+ let vm
+
+ afterEach(() => {
+ if (vm) {
+ vm.$parent.destroy()
+ vm = null
+ }
+ })
+
+ it('use', () => {
+ Vue.use(ScrollNav)
+ expect(Vue.component(ScrollNav.name))
+ .to.be.a('function')
+ })
+
+ it('should render correct contents', (done) => {
+ vm = createScrollNav()
+ setTimeout(() => {
+ expect(vm.$el.className)
+ .to.equal('cube-scroll-nav')
+ // prepend
+ expect(vm.$el.querySelector('.prepend-header').nextElementSibling.className)
+ .to.equal('cube-scroll-nav-main')
+
+ const navBarItems = vm.$el.querySelectorAll('.cube-scroll-nav-bar-item')
+ expect(navBarItems.length)
+ .to.equal(cityData.length)
+ expect(navBarItems[0].className)
+ .to.include('cube-scroll-nav-bar-item_active')
+
+ const panels = vm.$el.querySelectorAll('.cube-scroll-nav-panel')
+ expect(panels.length)
+ .to.equal(cityData.length)
+ expect(panels[0].querySelector('.cube-scroll-nav-panel-title').textContent.trim())
+ .to.equal(cityData[0].name)
+ done()
+ }, 50)
+ })
+
+ it('should render correct contents - sideStyle', (done) => {
+ vm = createScrollNav({
+ sideStyle: true,
+ current: cityData[1].name
+ }, {}, false)
+ setTimeout(() => {
+ expect(vm.$el.className)
+ .to.equal('cube-scroll-nav cube-scroll-nav_side')
+ expect(vm.$el.querySelector('.cube-scroll-nav-bar-item_active').textContent.trim())
+ .to.equal(cityData[1].name)
+ // sticky
+ expect(vm.$el.querySelector('.cube-sticky > .cube-sticky-fixed .cube-scroll-nav-bar'))
+ .not.to.be.null
+ expect(vm.$el.querySelector('.cube-scroll-wrapper > .cube-sticky-fixed .cube-scroll-nav-panel-title').textContent.trim())
+ .to.equal(cityData[1].name)
+ done()
+ }, 400)
+ })
+
+ it('should sticky & nav correctly', function (done) {
+ this.timeout(10000)
+ vm = createScrollNav()
+ setTimeout(() => {
+ expect(vm.active)
+ .to.equal(cityData[0].name)
+ // scroll to city 3
+ vm.$parent.current = cityData[2].name
+ setTimeout(() => {
+ expect(vm.active)
+ .to.equal(cityData[2].name)
+ // click bar
+ // to city 2
+ dispatchTap(vm.$el.querySelectorAll('.cube-scroll-nav-bar-item')[1])
+ setTimeout(() => {
+ expect(vm.active)
+ .to.equal(cityData[1].name)
+ // scroll to top 0
+ vm.$refs.scroll.scrollTo(0, 0, 50)
+ setTimeout(() => {
+ expect(vm.active)
+ .to.equal(cityData[0].name)
+ done()
+ }, 100)
+ }, 350)
+ }, 350)
+ }, 50)
+ })
+
+ it('should trigger events', function (done) {
+ this.timeout(10000)
+ const changeHandler = sinon.spy()
+ const stickyChangeHandler = sinon.spy()
+ vm = createScrollNav({
+ sideStyle: true,
+ current: cityData[1].name
+ }, {
+ onChange: changeHandler,
+ onStickyChange: stickyChangeHandler
+ }, false)
+ setTimeout(() => {
+ expect(stickyChangeHandler)
+ .to.be.calledOnce
+ expect(stickyChangeHandler)
+ .to.be.calledWith('cube-scroll-nav-bar')
+ const scroller = vm.$el.querySelector('.cube-scroll-wrapper')
+ dispatchSwipe(scroller, [
+ {
+ pageX: 300,
+ pageY: 300
+ },
+ {
+ pageX: 300,
+ pageY: 10
+ }
+ ], 100)
+ var scrollEnd = () => {
+ vm.$refs.scroll.scroll.off('scrollEnd', scrollEnd)
+ const targetActive = 'G'
+ expect(vm.active)
+ .to.equal(targetActive)
+ const index = findIndex(cityData, function (item) {
+ return item.name === targetActive
+ })
+ expect(changeHandler)
+ .to.have.callCount(index)
+ done()
+ }
+ setTimeout(() => {
+ vm.$refs.scroll.scroll.on('scrollEnd', scrollEnd)
+ }, 120)
+ }, 400)
+ })
+
+ function createScrollNav(props = {}, events = {}, showPrepend = true) {
+ const prepend = `
+
+ `
+ const data = {
+ sideStyle: false,
+ current: '',
+ data: cityData
+ }
+ Object.keys(props).forEach((k) => {
+ data[k] = props[k]
+ })
+ return createVue({
+ template: `
+
+
+ ${showPrepend ? prepend : ''}
+
+
+ -
+
+
{{city.cityid}}
+
{{city.name}}
+
{{city.tags}}
+
+
+
+
+
+
+ `,
+ data: data,
+ methods: {
+ changeHandler(current) {
+ events.onChange && events.onChange.call(this, current)
+ },
+ stickyChangeHandler(current) {
+ events.onStickyChange && events.onStickyChange.call(this, current)
+ }
+ }
+ })
+ }
+})
diff --git a/test/unit/specs/sticky.spec.js b/test/unit/specs/sticky.spec.js
new file mode 100644
index 000000000..0bda0f5d3
--- /dev/null
+++ b/test/unit/specs/sticky.spec.js
@@ -0,0 +1,242 @@
+import Vue from 'vue2'
+import Sticky from '@/modules/sticky'
+import createVue from '../utils/create-vue'
+
+describe('Sticky', () => {
+ let vm
+
+ afterEach(() => {
+ if (vm) {
+ vm.$parent.destroy()
+ vm = null
+ }
+ })
+
+ it('use', () => {
+ Vue.use(Sticky)
+ expect(Vue.component(Sticky.name))
+ .to.be.a('function')
+ })
+
+ it('should render correct contents', (done) => {
+ vm = createSticky()
+ expect(vm.$el.className)
+ .to.equal('cube-sticky')
+ const eles = vm.$el.querySelectorAll('.cube-sticky-ele')
+ expect(eles.length)
+ .to.equal(3)
+ expect(vm.$el.querySelector('.cube-sticky-fixed').style.display)
+ .to.equal('none')
+ setTimeout(() => {
+ done()
+ })
+ })
+
+ it('should fixed ele', (done) => {
+ vm = createSticky()
+ expect(vm.currentIndex)
+ .to.equal(-1)
+ expect(vm.currentKey)
+ .to.equal('')
+ setTimeout(() => {
+ vm.$parent.scrollTo(280)
+ setTimeout(() => {
+ expect(vm.currentIndex)
+ .to.equal(0)
+ expect(vm.currentKey)
+ .to.equal(0)
+ expect(vm.currentDiff)
+ .to.equal(250)
+ const fixed = vm.$el.querySelector('.cube-sticky-fixed')
+ expect(fixed.style.display)
+ .not.equal('none')
+ expect(fixed.textContent.trim())
+ .to.equal('111')
+ vm.$parent.scrollTo(500)
+ setTimeout(() => {
+ expect(vm.currentIndex)
+ .to.equal(1)
+ expect(vm.currentKey)
+ .to.equal('2')
+ expect(vm.currentDiff)
+ .to.equal(20)
+ vm.$parent.scrollTo(0)
+ setTimeout(() => {
+ expect(vm.currentIndex)
+ .to.equal(-1)
+ expect(vm.currentKey)
+ .to.equal('')
+ expect(vm.currentDiff)
+ .to.equal(0)
+ done()
+ })
+ })
+ })
+ })
+ })
+
+ it('should fixed ele - checkTop', (done) => {
+ vm = createSticky({
+ checkTop: false
+ }, {}, `
+
+ {{props.current}}
+
+ `)
+ expect(vm.currentIndex)
+ .to.equal(-1)
+ expect(vm.currentKey)
+ .to.equal('')
+ setTimeout(() => {
+ vm.$parent.scrollTo(280)
+ setTimeout(() => {
+ expect(vm.currentIndex)
+ .to.equal(0)
+ expect(vm.currentKey)
+ .to.equal(0)
+ expect(vm.currentDiff)
+ .to.equal(220)
+ const fixed = vm.$el.querySelector('.cube-sticky-fixed')
+ expect(fixed.style.display)
+ .not.equal('none')
+ expect(fixed.textContent.trim())
+ .to.equal('0')
+ vm.$parent.scrollTo(1000)
+ setTimeout(() => {
+ expect(vm.currentIndex)
+ .to.equal(2)
+ expect(vm.currentKey)
+ .to.equal('3')
+ expect(vm.currentDiff)
+ .to.equal(10)
+ vm.$parent.scrollTo(0)
+ setTimeout(() => {
+ expect(vm.currentIndex)
+ .to.equal(-1)
+ expect(vm.currentKey)
+ .to.equal('')
+ expect(vm.currentDiff)
+ .to.equal(0)
+ done()
+ })
+ })
+ })
+ })
+ })
+
+ it('should trigger events', (done) => {
+ const changeHandler = sinon.spy()
+ const onDiffChange = sinon.spy()
+ vm = createSticky(undefined, {
+ onChange: changeHandler,
+ onDiffChange: onDiffChange
+ })
+ setTimeout(() => {
+ vm.$parent.scrollTo(280)
+ setTimeout(() => {
+ expect(changeHandler)
+ .to.be.calledOnce
+ expect(changeHandler)
+ .to.be.calledWith(0)
+ expect(onDiffChange)
+ .to.be.calledOnce
+ vm.$parent.scrollTo(500)
+ setTimeout(() => {
+ expect(changeHandler)
+ .to.be.calledTwice
+ expect(changeHandler)
+ .to.be.calledWith('2')
+ expect(onDiffChange)
+ .to.be.calledTwice
+ done()
+ })
+ })
+ })
+ })
+
+ function createSticky(props = { checkTop: true }, events = {}, fixedSlot) {
+ const _data = [
+ '1',
+ '2',
+ '3',
+ '4',
+ '5',
+ '6',
+ '7',
+ '8',
+ '9',
+ '10',
+ '11',
+ '12',
+ '13',
+ '14'
+ ]
+ return createVue({
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${fixedSlot || ''}
+
+
+ `,
+ data: {
+ checkTop: props.checkTop,
+ scrollY: 0,
+ items: _data.concat(),
+ items2: _data.concat(),
+ items3: _data.concat()
+ },
+ methods: {
+ changeHandler(key, index) {
+ events.onChange && events.onChange.call(this, key, index)
+ },
+ diffChangeHandler(diff, height) {
+ events.onDiffChange && events.onDiffChange.call(this, diff, height)
+ },
+ scrollHandler(e) {
+ this.scrollY = e.currentTarget.scrollTop
+ },
+ scrollTo(y) {
+ this.$refs.scroller.scrollTop = y
+ }
+ }
+ })
+ }
+})
diff --git a/yarn.lock b/yarn.lock
index bfcbaaf5a..735f49a1c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1090,9 +1090,9 @@ better-assert@~1.0.0:
dependencies:
callsite "1.0.0"
-better-scroll@1.12.0:
- version "1.12.0"
- resolved "https://registry.yarnpkg.com/better-scroll/-/better-scroll-1.12.0.tgz#f15e1e316eb609690de6c733538b2b092aa3b118"
+better-scroll@^1.12.4:
+ version "1.12.4"
+ resolved "https://registry.yarnpkg.com/better-scroll/-/better-scroll-1.12.4.tgz#27d722fe7b6844176feb136da4f2fd39c70f973e"
dependencies:
babel-runtime "^6.0.0"