Skip to content

Commit

Permalink
Merge pull request ant-design#18327 from ant-design/feature
Browse files Browse the repository at this point in the history
Merge feature into master
  • Loading branch information
afc163 authored Aug 17, 2019
2 parents ced96b9 + 8345554 commit cfa7324
Show file tree
Hide file tree
Showing 50 changed files with 11,408 additions and 4,502 deletions.
13 changes: 13 additions & 0 deletions components/_util/__tests__/easings.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { easeInOutCubic } from '../easings';

describe('Test easings', () => {
it('easeInOutCubic return value', () => {
const nums = [];
// eslint-disable-next-line no-plusplus
for (let index = 0; index < 5; index++) {
nums.push(easeInOutCubic(index, 1, 5, 4));
}

expect(nums).toEqual([1, 1.25, 3, 4.75, 5]);
});
});
56 changes: 56 additions & 0 deletions components/_util/__tests__/scrollTo.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import scrollTo from '../scrollTo';

describe('Test ScrollTo function', () => {
let dateNowMock;

beforeAll(() => {
jest.useFakeTimers();
});

afterAll(() => {
jest.useRealTimers();
});

beforeEach(() => {
dateNowMock = jest
.spyOn(Date, 'now')
.mockImplementationOnce(() => 0)
.mockImplementationOnce(() => 1000);
});

afterEach(() => {
dateNowMock.mockRestore();
});

it('test scrollTo', async () => {
const scrollToSpy = jest.spyOn(window, 'scrollTo').mockImplementation((x, y) => {
window.scrollY = y;
window.pageYOffset = y;
});

scrollTo(1000);

jest.runAllTimers();
expect(window.pageYOffset).toBe(1000);

scrollToSpy.mockRestore();
});

it('test callback - option', async () => {
const cbMock = jest.fn();
scrollTo(1000, {
callback: cbMock,
});
jest.runAllTimers();
expect(cbMock).toHaveBeenCalledTimes(1);
});

it('test getContainer - option', async () => {
const div = document.createElement('div');
scrollTo(1000, {
getContainer: () => div,
});
jest.runAllTimers();
expect(div.scrollTop).toBe(1000);
});
});
9 changes: 9 additions & 0 deletions components/_util/easings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// eslint-disable-next-line import/prefer-default-export
export function easeInOutCubic(t: number, b: number, c: number, d: number) {
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;
}
37 changes: 37 additions & 0 deletions components/_util/scrollTo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import raf from 'raf';
import getScroll from './getScroll';
import { easeInOutCubic } from './easings';

interface ScrollToOptions {
/** Scroll container, default as window */
getContainer?: () => HTMLElement | Window;
/** Scroll end callback */
callback?: () => any;
/** Animation duration, default as 450 */
duration?: number;
}

export default function scrollTo(y: number, options: ScrollToOptions = {}) {
const { getContainer = () => window, callback, duration = 450 } = options;

const container = getContainer();
const scrollTop = getScroll(container, true);
const startTime = Date.now();

const frameFunc = () => {
const timestamp = Date.now();
const time = timestamp - startTime;
const nextScrollTop = easeInOutCubic(time > duration ? duration : time, scrollTop, y, duration);
if (container === window) {
window.scrollTo(window.pageXOffset, nextScrollTop);
} else {
(container as HTMLElement).scrollTop = nextScrollTop;
}
if (time < duration) {
raf(frameFunc);
} else if (typeof callback === 'function') {
callback();
}
};
raf(frameFunc);
}
101 changes: 44 additions & 57 deletions components/anchor/Anchor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import * as ReactDOM from 'react-dom';
import * as PropTypes from 'prop-types';
import classNames from 'classnames';
import addEventListener from 'rc-util/lib/Dom/addEventListener';
import raf from 'raf';
import Affix from '../affix';
import AnchorLink from './AnchorLink';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import scrollTo from '../_util/scrollTo';
import getScroll from '../_util/getScroll';

function getDefaultContainer() {
Expand Down Expand Up @@ -35,52 +35,7 @@ function getOffsetTop(element: HTMLElement, container: AnchorContainer): number
return rect.top;
}

function easeInOutCubic(t: number, b: number, c: number, d: number) {
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: string,
offsetTop = 0,
getContainer: () => AnchorContainer,
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 as HTMLElement).scrollTop = nextScrollTop;
}
if (time < 450) {
raf(frameFunc);
} else {
callback();
}
};
raf(frameFunc);
}

type Section = {
link: string;
Expand All @@ -99,10 +54,14 @@ export interface AnchorProps {
affix?: boolean;
showInkInFixed?: boolean;
getContainer?: () => AnchorContainer;
/** Return customize highlight anchor */
getCurrentAnchor?: () => string;
onClick?: (
e: React.MouseEvent<HTMLElement>,
link: { title: React.ReactNode; href: string },
) => void;
/** Scroll to target offset value, if none, it's offsetTop prop value or 0. */
targetOffset?: number;
}

export interface AnchorState {
Expand Down Expand Up @@ -205,6 +164,12 @@ export default class Anchor extends React.Component<AnchorProps, AnchorState> {
}

getCurrentAnchor(offsetTop = 0, bounds = 5): string {
const { getCurrentAnchor } = this.props;

if (typeof getCurrentAnchor === 'function') {
return getCurrentAnchor();
}

const activeLink = '';
if (typeof document === 'undefined') {
return activeLink;
Expand Down Expand Up @@ -237,6 +202,34 @@ export default class Anchor extends React.Component<AnchorProps, AnchorState> {
return '';
}

handleScrollTo = (link: string) => {
const { offsetTop, getContainer, targetOffset } = this.props as AnchorDefaultProps;

this.setState({ activeLink: 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,
});
};

saveInkNode = (node: HTMLSpanElement) => {
this.inkNode = node;
};
Expand All @@ -246,24 +239,18 @@ export default class Anchor extends React.Component<AnchorProps, AnchorState> {
return;
}
const { activeLink } = this.state;
const { offsetTop, bounds } = this.props;
const currentActiveLink = this.getCurrentAnchor(offsetTop, bounds);
const { offsetTop, bounds, targetOffset } = this.props;
const currentActiveLink = this.getCurrentAnchor(
targetOffset !== undefined ? targetOffset : offsetTop || 0,
bounds,
);
if (activeLink !== currentActiveLink) {
this.setState({
activeLink: currentActiveLink,
});
}
};

handleScrollTo = (link: string) => {
const { offsetTop, getContainer } = this.props as AnchorDefaultProps;
this.animating = true;
this.setState({ activeLink: link });
scrollTo(link, offsetTop, getContainer, () => {
this.animating = false;
});
};

updateInk = () => {
if (typeof document === 'undefined') {
return;
Expand Down
Loading

0 comments on commit cfa7324

Please sign in to comment.