From ef720db420c0b0f561f295a8ef8a2fbe4d4c82c8 Mon Sep 17 00:00:00 2001 From: RaoHai Date: Fri, 28 Oct 2016 14:02:55 +0800 Subject: [PATCH] anchor --- components/affix/demo/basic.md | 2 +- components/anchor/AnchorLink.tsx | 21 ++++ components/anchor/demo/basic.md | 27 ++++++ components/anchor/demo/independ.md | 24 +++++ components/anchor/index.en-US.md | 29 ++++++ components/anchor/index.tsx | 128 +++++++++++++++++++++++++ components/anchor/index.zh-CN.md | 31 ++++++ components/anchor/style/index.less | 5 + components/anchor/style/index.tsx | 2 + components/anchor/style/index.zh-CN.md | 31 ++++++ components/index.tsx | 4 +- 11 files changed, 302 insertions(+), 2 deletions(-) create mode 100644 components/anchor/AnchorLink.tsx create mode 100644 components/anchor/demo/basic.md create mode 100644 components/anchor/demo/independ.md create mode 100644 components/anchor/index.en-US.md create mode 100644 components/anchor/index.tsx create mode 100644 components/anchor/index.zh-CN.md create mode 100644 components/anchor/style/index.less create mode 100644 components/anchor/style/index.tsx create mode 100644 components/anchor/style/index.zh-CN.md diff --git a/components/affix/demo/basic.md b/components/affix/demo/basic.md index 59c8605560..fc47830b77 100644 --- a/components/affix/demo/basic.md +++ b/components/affix/demo/basic.md @@ -14,7 +14,7 @@ title: The simplest usage. ````jsx -import { Affix, Button } from 'antd'; +import { Anchor } from 'antd'; ReactDOM.render(
diff --git a/components/anchor/AnchorLink.tsx b/components/anchor/AnchorLink.tsx new file mode 100644 index 0000000000..ee912467ad --- /dev/null +++ b/components/anchor/AnchorLink.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + + +export interface AnchorLinkProps { + href: string; + onClick: (href: string) => {}; + active: boolean; +} + +export default class AnchorLink extends React.Component { + onClick = () => { + if (this.props.href) { + this.props.onClick(this.props.href); + } + } + render() { + return
+ {this.props.active ? 'active':null } {this.props.children} +
; + } +} \ No newline at end of file diff --git a/components/anchor/demo/basic.md b/components/anchor/demo/basic.md new file mode 100644 index 0000000000..8ead991dd9 --- /dev/null +++ b/components/anchor/demo/basic.md @@ -0,0 +1,27 @@ +--- +order: 0 +title: + zh-CN: 基本 + en-US: Basic +--- + +## zh-CN + +最简单的用法。 + +## en-US + +The simplest usage. + +```jsx + +const { Anchor } = antd; + +const { AnchorLink } = Anchor; + +ReactDOM.render( + + 锚点1 + +, mountNode); +``` \ No newline at end of file diff --git a/components/anchor/demo/independ.md b/components/anchor/demo/independ.md new file mode 100644 index 0000000000..fecdcf2d45 --- /dev/null +++ b/components/anchor/demo/independ.md @@ -0,0 +1,24 @@ +--- +order: 1 +title: + zh-CN: 独立使用 AnchorLink + en-US: Independent AnchorLink +--- + +## zh-CN + +独立使用 AnchorLink + +## en-US + +Independent AnchorLink + +```jsx + +const { Anchor } = antd; +const { AnchorLink } = Anchor; + +ReactDOM.render( + 锚点1 +, mountNode); +``` \ No newline at end of file diff --git a/components/anchor/index.en-US.md b/components/anchor/index.en-US.md new file mode 100644 index 0000000000..8e082887d9 --- /dev/null +++ b/components/anchor/index.en-US.md @@ -0,0 +1,29 @@ +--- +category: Components +type: Other +title: Anchor +--- + +Make an element sticky to viewport. + +## When To Use + +When user browses a long web page, some content need to sticky to viewport. It is common for menus and actions. + +Please note that Affix should not cover other content in page, especially when the size of viewport is small. + +## API + +| Property | Description | Type | Default | +|--------------|-----------------------|----------|--------------| +| offsetTop | Pixels to offset from top when calculating position of scroll | Number | 0 | +| offsetBottom | Pixels to offset from bottom when calculating position of scroll | Number | - | +| onChange | Callback when affix state is changed | Function(affixed) | - | + +**Note:** Children of `Affix` can not be `position: absolute`, but you can set `Affix` as `position: absolute`: + +```jsx + + ... + +``` diff --git a/components/anchor/index.tsx b/components/anchor/index.tsx new file mode 100644 index 0000000000..2efd17d26a --- /dev/null +++ b/components/anchor/index.tsx @@ -0,0 +1,128 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import addEventListener from 'rc-util/lib/Dom/addEventListener'; +import getScroll from '../_util/getScroll'; +import AnchorLink from './AnchorLink'; +import Affix from '../affix'; +import getRequestAnimationFrame from '../_util/getRequestAnimationFrame'; + +const reqAnimFrame = getRequestAnimationFrame(); + +const easeInOutCubic = (t, b, c, d) => { + t /= d/2; + if (t < 1) return c/2*t*t*t + b; + t -= 2; + return c/2*(t*t*t + 2) + b; +}; + +function getDefaultTarget() { + return typeof window !== 'undefined' ? + window : null; +} + +function getOffsetTop(element): number { + if (!element) { + return 0; + } + + if (!element.getClientRects().length) { + return 0; + } + + const rect = element.getBoundingClientRect(); + + if ( rect.width || rect.height ) { + const doc = element.ownerDocument; + const docElem = doc.documentElement; + return rect.top + window.pageYOffset - docElem.clientTop; + } + + return rect.top; +} + +export interface AnchorProps { + target: () => HTMLElement | Window; + children: React.ReactNode; +} + + +export default class Anchor extends React.Component { + static AnchorLink = AnchorLink; + + private scrollEvent: any; + private sections: Array = []; + + constructor(props) { + super(props); + this.state = { + activeAnchor: null, + }; + } + handleScroll = () => { + const { target = getDefaultTarget } = this.props; + const scrollTop = getScroll(target(), true); + let activeAnchor; + + this.sections.forEach(section => { + const target = document.querySelector(section); + if (target) { + const top = target.offsetTop; + const bottom = top + target.clientHeight; + if ((scrollTop >= top) && (scrollTop <= bottom)) { + activeAnchor = section; + } + } + }); + + if (activeAnchor) { + this.setState({ + activeAnchor, + }); + } + } + + componentDidMount() { + this.handleScroll(); + this.scrollEvent = addEventListener((this.props.target || getDefaultTarget)(), 'scroll', this.handleScroll); + } + + componentWillUnmount() { + if (this.scrollEvent) { + this.scrollEvent.remove(); + } + } + + scrollTo = (href) => { + const { target = getDefaultTarget } = this.props; + const scrollTop = getScroll(target(), true); + const offsetTop = getOffsetTop(document.querySelector(href)); + const startTime = Date.now(); + const frameFunc = () => { + const timestamp = Date.now(); + const time = timestamp - startTime; + document.body.scrollTop = easeInOutCubic(time, scrollTop, offsetTop, 450); + if (time < 450) { + reqAnimFrame(frameFunc); + } + }; + reqAnimFrame(frameFunc); + } + + renderAnchorLink = (child) => { + const { href } = child.props; + if (href) { + if (this.sections.indexOf(href) === -1) { + this.sections.push(href); + } + return React.cloneElement(child, { onClick: this.scrollTo, active: this.state.activeAnchor === href }); + } + return child; + } + + render() { + return +
{React.Children.map(this.props.children, this.renderAnchorLink)}
+
; + } +} + diff --git a/components/anchor/index.zh-CN.md b/components/anchor/index.zh-CN.md new file mode 100644 index 0000000000..9912071da8 --- /dev/null +++ b/components/anchor/index.zh-CN.md @@ -0,0 +1,31 @@ +--- +category: Components +subtitle: 锚点 +type: Other +title: Anchor +--- + +将页面元素钉在可视范围。 + +## 何时使用 + +当内容区域比较长,需要滚动页面时,这部分内容对应的操作或者导航需要在滚动范围内始终展现。常用于侧边菜单和按钮组合。 + +页面可视范围过小时,慎用此功能以免遮挡页面内容。 + +## API + +| 成员 | 说明 | 类型 | 默认值 | +|-------------|----------------|--------------------|--------------| +| offsetTop | 距离窗口顶部达到指定偏移量后触发 | Number | | +| offsetBottom | 距离窗口底部达到指定偏移量后触发 | Number | | +| target | 设置 `Affix` 需要监听其滚动事件的元素,值为一个返回对应 DOM 元素的函数 | Function | () => window | +| onChange | 固定状态改变时触发的回调函数 | Function(affixed) | 无 | + +**注意:**`Affix` 内的元素不要使用绝对定位,如需要绝对定位的效果,可以直接设置 `Affix` 为绝对定位: + +```jsx + + ... + +``` diff --git a/components/anchor/style/index.less b/components/anchor/style/index.less new file mode 100644 index 0000000000..34b46f360b --- /dev/null +++ b/components/anchor/style/index.less @@ -0,0 +1,5 @@ +@import "../../style/themes/default"; + +.@{ant-prefix}-anchor { + color: red; +} diff --git a/components/anchor/style/index.tsx b/components/anchor/style/index.tsx new file mode 100644 index 0000000000..3a3ab0de59 --- /dev/null +++ b/components/anchor/style/index.tsx @@ -0,0 +1,2 @@ +import '../../style/index.less'; +import './index.less'; diff --git a/components/anchor/style/index.zh-CN.md b/components/anchor/style/index.zh-CN.md new file mode 100644 index 0000000000..531bed8ddd --- /dev/null +++ b/components/anchor/style/index.zh-CN.md @@ -0,0 +1,31 @@ +--- +category: Components +subtitle: 固钉 +type: Other +title: Affix +--- + +将页面元素钉在可视范围。 + +## 何时使用 + +当内容区域比较长,需要滚动页面时,这部分内容对应的操作或者导航需要在滚动范围内始终展现。常用于侧边菜单和按钮组合。 + +页面可视范围过小时,慎用此功能以免遮挡页面内容。 + +## API + +| 成员 | 说明 | 类型 | 默认值 | +|-------------|----------------|--------------------|--------------| +| offsetTop | 距离窗口顶部达到指定偏移量后触发 | Number | | +| offsetBottom | 距离窗口底部达到指定偏移量后触发 | Number | | +| target | 设置 `Affix` 需要监听其滚动事件的元素,值为一个返回对应 DOM 元素的函数 | Function | () => window | +| onChange | 固定状态改变时触发的回调函数 | Function(affixed) | 无 | + +**注意:**`Affix` 内的元素不要使用绝对定位,如需要绝对定位的效果,可以直接设置 `Affix` 为绝对定位: + +```jsx + + ... + +``` diff --git a/components/index.tsx b/components/index.tsx index 6dca06cac8..c537d6169c 100644 --- a/components/index.tsx +++ b/components/index.tsx @@ -9,6 +9,8 @@ please use https://www.npmjs.com/package/babel-plugin-import to reduce app bundl export { default as Affix } from './affix'; +export { default as Anchor } from './anchor'; + export { default as AutoComplete } from './auto-complete'; export { default as Alert } from './alert'; @@ -101,4 +103,4 @@ export { default as Tooltip } from './tooltip'; export { default as Mention } from './mention'; -export { default as Upload } from './upload'; +export { default as Upload } from './upload'; \ No newline at end of file