From 22b28b34c2ffda4714e1578a536a1a230c15070f Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Sat, 8 Feb 2020 13:07:57 +0800 Subject: [PATCH] feat: update anchor --- build/config.js | 2 +- components/anchor/Anchor.jsx | 185 +++++++++++------- components/anchor/AnchorLink.jsx | 4 +- .../__tests__/__snapshots__/demo.test.js.snap | 47 +++++ components/anchor/demo/basic.md | 1 + components/anchor/demo/customizeHighlight.md | 31 +++ components/anchor/demo/index.vue | 6 + components/anchor/demo/onChange.md | 31 +++ components/anchor/demo/static.md | 2 +- components/anchor/demo/targetOffset.md | 34 ++++ components/anchor/index.en-US.md | 38 ++-- components/anchor/index.zh-CN.md | 38 ++-- tests/utils.js | 1 + 13 files changed, 314 insertions(+), 106 deletions(-) create mode 100644 components/anchor/demo/customizeHighlight.md create mode 100644 components/anchor/demo/onChange.md create mode 100644 components/anchor/demo/targetOffset.md diff --git a/build/config.js b/build/config.js index 45fccbd57..24a3cbe38 100644 --- a/build/config.js +++ b/build/config.js @@ -1,5 +1,5 @@ module.exports = { dev: { - componentName: 'alert', // dev components + componentName: 'anchor', // dev components }, }; diff --git a/components/anchor/Anchor.jsx b/components/anchor/Anchor.jsx index b8412d6ed..485bb9e22 100644 --- a/components/anchor/Anchor.jsx +++ b/components/anchor/Anchor.jsx @@ -2,8 +2,8 @@ import PropTypes from '../_util/vue-types'; import classNames from 'classnames'; import addEventListener from '../vc-util/Dom/addEventListener'; import Affix from '../affix'; +import scrollTo from '../_util/scrollTo'; import getScroll from '../_util/getScroll'; -import raf from 'raf'; import { initDefaultProps } from '../_util/props-util'; import BaseMixin from '../_util/BaseMixin'; import { ConfigConsumerProps } from '../config-provider'; @@ -34,47 +34,47 @@ function getOffsetTop(element, container) { return rect.top; } -function easeInOutCubic(t, b, c, d) { - const cc = c - b; - t /= d / 2; - if (t < 1) { - return (cc / 2) * t * t * t + b; - } - return (cc / 2) * ((t -= 2) * t * t + 2) + b; -} +// function easeInOutCubic(t, b, c, d) { +// const cc = c - b; +// t /= d / 2; +// if (t < 1) { +// return (cc / 2) * t * t * t + b; +// } +// return (cc / 2) * ((t -= 2) * t * t + 2) + b; +// } const sharpMatcherRegx = /#([^#]+)$/; -function scrollTo(href, offsetTop = 0, getContainer, callback = () => {}) { - const container = getContainer(); - const scrollTop = getScroll(container, true); - const sharpLinkMatch = sharpMatcherRegx.exec(href); - if (!sharpLinkMatch) { - return; - } - const targetElement = document.getElementById(sharpLinkMatch[1]); - if (!targetElement) { - return; - } - const eleOffsetTop = getOffsetTop(targetElement, container); - const targetScrollTop = scrollTop + eleOffsetTop - offsetTop; - const startTime = Date.now(); - const frameFunc = () => { - const timestamp = Date.now(); - const time = timestamp - startTime; - const nextScrollTop = easeInOutCubic(time, scrollTop, targetScrollTop, 450); - if (container === window) { - window.scrollTo(window.pageXOffset, nextScrollTop); - } else { - container.scrollTop = nextScrollTop; - } - if (time < 450) { - raf(frameFunc); - } else { - callback(); - } - }; - raf(frameFunc); -} +// function scrollTo(href, offsetTop = 0, getContainer, callback = () => {}) { +// const container = getContainer(); +// const scrollTop = getScroll(container, true); +// const sharpLinkMatch = sharpMatcherRegx.exec(href); +// if (!sharpLinkMatch) { +// return; +// } +// const targetElement = document.getElementById(sharpLinkMatch[1]); +// if (!targetElement) { +// return; +// } +// const eleOffsetTop = getOffsetTop(targetElement, container); +// const targetScrollTop = scrollTop + eleOffsetTop - offsetTop; +// const startTime = Date.now(); +// const frameFunc = () => { +// const timestamp = Date.now(); +// const time = timestamp - startTime; +// const nextScrollTop = easeInOutCubic(time, scrollTop, targetScrollTop, 450); +// if (container === window) { +// window.scrollTo(window.pageXOffset, nextScrollTop); +// } else { +// container.scrollTop = nextScrollTop; +// } +// if (time < 450) { +// raf(frameFunc); +// } else { +// callback(); +// } +// }; +// raf(frameFunc); +// } export const AnchorProps = { prefixCls: PropTypes.string, @@ -85,6 +85,8 @@ export const AnchorProps = { getContainer: PropTypes.func, wrapperClass: PropTypes.string, wrapperStyle: PropTypes.object, + getCurrentAnchor: PropTypes.func, + targetOffset: PropTypes.number, }; export default { @@ -130,43 +132,38 @@ export default { mounted() { this.$nextTick(() => { const { getContainer } = this; - this.scrollEvent = addEventListener(getContainer(), 'scroll', this.handleScroll); + this.scrollContainer = getContainer(); + this.scrollEvent = addEventListener(this.scrollContainer, 'scroll', this.handleScroll); this.handleScroll(); }); }, - + updated() { + this.$nextTick(() => { + if (this.scrollEvent) { + const { getContainer } = this; + const currentContainer = getContainer(); + if (this.scrollContainer !== currentContainer) { + this.scrollContainer = currentContainer; + this.scrollEvent.remove(); + this.scrollEvent = addEventListener(this.scrollContainer, 'scroll', this.handleScroll); + this.handleScroll(); + } + } + this.updateInk(); + }); + }, beforeDestroy() { if (this.scrollEvent) { this.scrollEvent.remove(); } }, - - updated() { - this.$nextTick(() => { - this.updateInk(); - }); - }, methods: { - handleScroll() { - if (this.animating) { - return; + getCurrentActiveLink(offsetTop = 0, bounds = 5) { + const { getCurrentAnchor } = this; + + if (typeof getCurrentAnchor === 'function') { + return getCurrentAnchor(); } - const { offsetTop, bounds } = this; - this.setState({ - activeLink: this.getCurrentAnchor(offsetTop, bounds), - }); - }, - - handleScrollTo(link) { - const { offsetTop, getContainer } = this; - this.animating = true; - this.setState({ activeLink: link }); - scrollTo(link, offsetTop, getContainer, () => { - this.animating = false; - }); - }, - - getCurrentAnchor(offsetTop = 0, bounds = 5) { const activeLink = ''; if (typeof document === 'undefined') { return activeLink; @@ -199,6 +196,56 @@ export default { return ''; }, + handleScrollTo(link) { + const { offsetTop, getContainer, targetOffset } = this; + + this.setCurrentActiveLink(link); + const container = getContainer(); + const scrollTop = getScroll(container, true); + const sharpLinkMatch = sharpMatcherRegx.exec(link); + if (!sharpLinkMatch) { + return; + } + const targetElement = document.getElementById(sharpLinkMatch[1]); + if (!targetElement) { + return; + } + + const eleOffsetTop = getOffsetTop(targetElement, container); + let y = scrollTop + eleOffsetTop; + y -= targetOffset !== undefined ? targetOffset : offsetTop || 0; + this.animating = true; + + scrollTo(y, { + callback: () => { + this.animating = false; + }, + getContainer, + }); + }, + setCurrentActiveLink(link) { + const { activeLink } = this; + + if (activeLink !== link) { + this.setState({ + activeLink: link, + }); + this.$emit('change', link); + } + }, + + handleScroll() { + if (this.animating) { + return; + } + const { offsetTop, bounds, targetOffset } = this; + const currentActiveLink = this.getCurrentActiveLink( + targetOffset !== undefined ? targetOffset : offsetTop || 0, + bounds, + ); + this.setCurrentActiveLink(currentActiveLink); + }, + updateInk() { if (typeof document === 'undefined') { return; @@ -206,7 +253,7 @@ export default { const { _sPrefixCls } = this; const linkNode = this.$el.getElementsByClassName(`${_sPrefixCls}-link-title-active`)[0]; if (linkNode) { - this.$refs.linkNode.style.top = `${linkNode.offsetTop + linkNode.clientHeight / 2 - 4.5}px`; + this.$refs.inkNode.style.top = `${linkNode.offsetTop + linkNode.clientHeight / 2 - 4.5}px`; } }, }, @@ -245,7 +292,7 @@ export default {
- +
{$slots.default}
diff --git a/components/anchor/AnchorLink.jsx b/components/anchor/AnchorLink.jsx index 6d1655d44..ca36495f2 100644 --- a/components/anchor/AnchorLink.jsx +++ b/components/anchor/AnchorLink.jsx @@ -7,6 +7,7 @@ export const AnchorLinkProps = { prefixCls: PropTypes.string, href: PropTypes.string, title: PropTypes.any, + target: PropTypes.string, }; export default { @@ -47,7 +48,7 @@ export default { }, }, render() { - const { prefixCls: customizePrefixCls, href, $slots } = this; + const { prefixCls: customizePrefixCls, href, $slots, target } = this; const getPrefixCls = this.configProvider.getPrefixCls; const prefixCls = getPrefixCls('anchor', customizePrefixCls); @@ -66,6 +67,7 @@ export default { class={titleClassName} href={href} title={typeof title === 'string' ? title : ''} + target={target} onClick={this.handleClick} > {title} diff --git a/components/anchor/__tests__/__snapshots__/demo.test.js.snap b/components/anchor/__tests__/__snapshots__/demo.test.js.snap index 2d396de52..d1e5d2c4e 100644 --- a/components/anchor/__tests__/__snapshots__/demo.test.js.snap +++ b/components/anchor/__tests__/__snapshots__/demo.test.js.snap @@ -8,6 +8,7 @@ exports[`renders ./components/anchor/demo/basic.md correctly 1`] = `
+ `; +exports[`renders ./components/anchor/demo/customizeHighlight.md correctly 1`] = ` +
+ +
+`; + +exports[`renders ./components/anchor/demo/onChange.md correctly 1`] = ` +
+ +
+`; + exports[`renders ./components/anchor/demo/onClick.md correctly 1`] = `
@@ -45,3 +74,21 @@ exports[`renders ./components/anchor/demo/static.md correctly 1`] = `
`; + +exports[`renders ./components/anchor/demo/targetOffset.md correctly 1`] = ` +
+
+ +
+
+`; diff --git a/components/anchor/demo/basic.md b/components/anchor/demo/basic.md index be91e4374..87df84e25 100644 --- a/components/anchor/demo/basic.md +++ b/components/anchor/demo/basic.md @@ -13,6 +13,7 @@ The simplest usage. + diff --git a/components/anchor/demo/customizeHighlight.md b/components/anchor/demo/customizeHighlight.md new file mode 100644 index 000000000..9ba3e5d40 --- /dev/null +++ b/components/anchor/demo/customizeHighlight.md @@ -0,0 +1,31 @@ + +#### 自定义锚点高亮 +自定义锚点高亮。 + + + +#### Customize the anchor highlight +Customize the anchor highlight. + + +```tpl + + +``` diff --git a/components/anchor/demo/index.vue b/components/anchor/demo/index.vue index e94258489..d0c08292b 100644 --- a/components/anchor/demo/index.vue +++ b/components/anchor/demo/index.vue @@ -2,6 +2,9 @@ import Basic from './basic'; import Static from './static'; import OnClick from './onClick'; +import CustomizeHighlight from './customizeHighlight'; +import OnChange from './OnChange'; +import TargetOffset from './targetOffset'; import CN from '../index.zh-CN.md'; import US from '../index.en-US.md'; @@ -37,6 +40,9 @@ export default { + + + diff --git a/components/anchor/demo/onChange.md b/components/anchor/demo/onChange.md new file mode 100644 index 000000000..2f8a0d5d1 --- /dev/null +++ b/components/anchor/demo/onChange.md @@ -0,0 +1,31 @@ + +#### 监听锚点链接改变 +监听锚点链接改变 + + + +#### Listening for anchor link change +Listening for anchor link change. + + +```tpl + + +``` diff --git a/components/anchor/demo/static.md b/components/anchor/demo/static.md index a071d7c00..3c4da5d2e 100644 --- a/components/anchor/demo/static.md +++ b/components/anchor/demo/static.md @@ -4,7 +4,7 @@ -#### Static Anchor +#### Static Do not change state when page is scrolling. diff --git a/components/anchor/demo/targetOffset.md b/components/anchor/demo/targetOffset.md new file mode 100644 index 000000000..f6502b45d --- /dev/null +++ b/components/anchor/demo/targetOffset.md @@ -0,0 +1,34 @@ + +#### 设置锚点滚动偏移量 +锚点目标滚动到屏幕正中间。 + + + +#### Set Anchor scroll offset +Anchor target scroll to screen center. + + +```tpl + + +``` diff --git a/components/anchor/index.en-US.md b/components/anchor/index.en-US.md index 2863a90fd..6176631be 100644 --- a/components/anchor/index.en-US.md +++ b/components/anchor/index.en-US.md @@ -2,26 +2,30 @@ ### Anchor Props -| Property | Description | Type | Default | -| --- | --- | --- | --- | -| affix | Fixed mode of Anchor | boolean | true | -| bounds | Bounding distance of anchor area | number | 5(px) | -| getContainer | Scrolling container | () => HTMLElement | () => window | -| offsetBottom | Pixels to offset from bottom when calculating position of scroll | number | - | -| offsetTop | Pixels to offset from top when calculating position of scroll | number | 0 | -| showInkInFixed | Whether show ink-balls in Fixed mode | boolean | false | -| wrapperClass | The class name of the container | string | - | -| wrapperStyle | The style of the container | object | - | +| Property | Description | Type | Default | Version | +| --- | --- | --- | --- | --- | +| affix | Fixed mode of Anchor | boolean | true | | +| bounds | Bounding distance of anchor area | number | 5(px) | | +| getContainer | Scrolling container | () => HTMLElement | () => window | | +| offsetBottom | Pixels to offset from bottom when calculating position of scroll | number | - | | +| offsetTop | Pixels to offset from top when calculating position of scroll | number | 0 | | +| showInkInFixed | Whether show ink-balls in Fixed mode | boolean | false | | +| wrapperClass | The class name of the container | string | - | | +| wrapperStyle | The style of the container | object | - | | +| getCurrentAnchor | Customize the anchor highlight | () => string | - | 1.5.0 | +| targetOffset | Anchor scroll offset, default as `offsetTop`, [example](#components-anchor-demo-targetOffset) | number | `offsetTop` | 1.5.0 | ### Events -| Events Name | Description | Arguments | -| ----------- | --------------------------------------- | -------------------------------- | -| click | set the handler to handle `click` event | Function(e: Event, link: Object) | +| Events Name | Description | Arguments | Version | +| --- | --- | --- | --- | +| click | set the handler to handle `click` event | Function(e: Event, link: Object) | | +| change | Listening for anchor link change | (currentActiveLink: string) => void | | 1.5.0 | ### Link Props -| Property | Description | Type | Default | -| -------- | -------------------- | ------------ | ------- | -| href | target of hyperlink | string | | -| title | content of hyperlink | string\|slot | | +| Property | Description | Type | Default | Version | +| -------- | ----------------------------------------- | ------------ | ------- | ------- | +| href | target of hyperlink | string | | | +| title | content of hyperlink | string\|slot | | | +| target | Specifies where to display the linked URL | string | | | diff --git a/components/anchor/index.zh-CN.md b/components/anchor/index.zh-CN.md index 69957fa56..5c448c700 100644 --- a/components/anchor/index.zh-CN.md +++ b/components/anchor/index.zh-CN.md @@ -2,26 +2,30 @@ ### Anchor Props -| 成员 | 说明 | 类型 | 默认值 | -| -------------- | -------------------------------- | ----------------- | ------------ | -| affix | 固定模式 | boolean | true | -| bounds | 锚点区域边界 | number | 5(px) | -| getContainer | 指定滚动的容器 | () => HTMLElement | () => window | -| offsetBottom | 距离窗口底部达到指定偏移量后触发 | number | | -| offsetTop | 距离窗口顶部达到指定偏移量后触发 | number | | -| showInkInFixed | 固定模式是否显示小圆点 | boolean | false | -| wrapperClass | 容器的类名 | string | - | -| wrapperStyle | 容器样式 | object | - | +| 成员 | 说明 | 类型 | 默认值 | 版本 | +| --- | --- | --- | --- | --- | +| affix | 固定模式 | boolean | true | | +| bounds | 锚点区域边界 | number | 5(px) | | +| getContainer | 指定滚动的容器 | () => HTMLElement | () => window | | +| offsetBottom | 距离窗口底部达到指定偏移量后触发 | number | | | +| offsetTop | 距离窗口顶部达到指定偏移量后触发 | number | | | +| showInkInFixed | 固定模式是否显示小圆点 | boolean | false | | +| wrapperClass | 容器的类名 | string | - | | +| wrapperStyle | 容器样式 | object | - | | +| getCurrentAnchor | 自定义高亮的锚点 | () => string | - | 1.5.0 | +| targetOffset | 锚点滚动偏移量,默认与 offsetTop 相同,[例子](#components-anchor-demo-targetOffset) | number | `offsetTop` | 1.5.0 | ### 事件 -| 事件名称 | 说明 | 回调参数 | -| -------- | ---------------------- | -------------------------------- | -| click | `click` 事件的 handler | Function(e: Event, link: Object) | +| 事件名称 | 说明 | 回调参数 | 版本 | +| -------- | ---------------------- | ----------------------------------- | ---- | +| click | `click` 事件的 handler | Function(e: Event, link: Object) | | +| change | 监听锚点链接改变 | (currentActiveLink: string) => void | | 1.5.0 | ### Link Props -| 成员 | 说明 | 类型 | 默认值 | -| ----- | -------- | ------------ | ------ | -| href | 锚点链接 | string | | -| title | 文字内容 | string\|slot | | +| 成员 | 说明 | 类型 | 默认值 | 版本 | +| ------ | -------------------------------- | ------------ | ------ | ---- | +| href | 锚点链接 | string | | | +| title | 文字内容 | string\|slot | | | +| target | 该属性指定在何处显示链接的资源。 | string | | | diff --git a/tests/utils.js b/tests/utils.js index 9a70f4b23..bb99b93bd 100644 --- a/tests/utils.js +++ b/tests/utils.js @@ -25,3 +25,4 @@ export function asyncExpect(fn, timeout) { } }); } +export const sleep = (timeout = 0) => new Promise(resolve => setTimeout(resolve, timeout));