Skip to content

Commit

Permalink
feat: add ScrollLocker
Browse files Browse the repository at this point in the history
  • Loading branch information
shaodahong committed Dec 15, 2020
1 parent 37aacd6 commit 54457b2
Show file tree
Hide file tree
Showing 2 changed files with 231 additions and 0 deletions.
108 changes: 108 additions & 0 deletions src/Dom/scrollLocker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import getScrollBarSize from '../getScrollBarSize';
import setStyle from '../setStyle';

export interface scrollLockOptions {
container: HTMLElement;
}

let uuid = 0;

interface Ilocks {
target: typeof uuid;
cacheStyle?: React.CSSProperties;
options: scrollLockOptions;
}

let locks: Ilocks[] = [];
const scrollingEffectClassName = 'ant-scrolling-effect';
const scrollingEffectClassNameReg = new RegExp(
`${scrollingEffectClassName}`,
'g',
);

export default class ScrollLocker {
lockTarget: typeof uuid;

options: scrollLockOptions;

constructor(options?: scrollLockOptions) {
// eslint-disable-next-line no-plusplus
this.lockTarget = uuid++;
this.options = options;
}

lock() {
// If lockTarget exist return
if (locks.some(({ target }) => target === this.lockTarget)) {
return;
}

// If same container effect, return
if (
locks.some(
({ options }) => options?.container === this.options?.container,
)
) {
locks = [...locks, { target: this.lockTarget, options: this.options }];
return;
}

// Add Effect
const scrollBarSize = getScrollBarSize();
const container = this.options?.container || document.body;
const containerClassName = container.className;

const cacheStyle = setStyle(
{
position: 'relative',
width: `calc(100% - ${scrollBarSize}px)`,
},
{
element: container,
},
);

// https://github.com/ant-design/ant-design/issues/19729
if (!scrollingEffectClassNameReg.test(containerClassName)) {
const addClassName = `${containerClassName} ${scrollingEffectClassName}`;
container.className = addClassName.trim();
}

locks = [
...locks,
{ target: this.lockTarget, options: this.options, cacheStyle },
];
}

unLock() {
const findLock = locks.find(({ target }) => target === this.lockTarget);

locks = locks.filter(({ target }) => target !== this.lockTarget);

if (
!findLock ||
locks.some(
({ options }) => options?.container === findLock.options?.container,
)
) {
return;
}

// Remove Effect
const container = this.options?.container || document.body;
const containerClassName = container.className;

if (!scrollingEffectClassNameReg.test(containerClassName)) return;
setStyle(
// @ts-ignore position should be empty string
findLock.cacheStyle || {
position: '',
width: '',
},
{ element: container },
);
container.className = container.className
.replace(scrollingEffectClassNameReg, '')
.trim();
}
}
123 changes: 123 additions & 0 deletions tests/scrollLocker.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import ScrollLocker from '../src/Dom/scrollLocker';

jest.mock('../src/getScrollBarSize', () =>
jest.fn().mockImplementation(() => 20),
);

describe('ScrollLocker', () => {
const effectClassname = 'ant-scrolling-effect';
let scrollLocker: ScrollLocker;

// https://github.com/jsdom/jsdom/issues/1332
// JSDom eats `calc`, so we don't test it.
const effectStyle = 'position: relative;';
beforeEach(() => {
scrollLocker = new ScrollLocker();
});

afterEach(() => {
if (scrollLocker) {
scrollLocker.unLock();
}
});

it('Lock and unLock', () => {
scrollLocker.lock();

expect(document.body.className).toBe(effectClassname);
expect(document.body.getAttribute('style')).toBe(effectStyle);

scrollLocker.unLock();

expect(document.body.className).toBe('');
expect(document.body.getAttribute('style')).toBe('');
});

it('Lock multiple same target and unLock', () => {
scrollLocker.lock();
scrollLocker.lock();
scrollLocker.lock();

expect(document.body.className).toBe(effectClassname);
expect(document.body.getAttribute('style')).toBe(effectStyle);

scrollLocker.unLock();

expect(document.body.className).toBe('');
expect(document.body.getAttribute('style')).toBe('');

scrollLocker.unLock();

expect(document.body.className).toBe('');
expect(document.body.getAttribute('style')).toBe('');
});

it('Lock multiple different target and unLock', () => {
const locker1 = new ScrollLocker();
const locker2 = new ScrollLocker();

scrollLocker.lock();
locker1.lock();
locker2.lock();

expect(document.body.className).toBe(effectClassname);
expect(document.body.getAttribute('style')).toBe(effectStyle);

locker2.unLock();

expect(document.body.className).toBe(effectClassname);
expect(document.body.getAttribute('style')).toBe(effectStyle);

locker1.unLock();

expect(document.body.className).toBe(effectClassname);
expect(document.body.getAttribute('style')).toBe(effectStyle);

scrollLocker.unLock();

expect(document.body.className).toBe('');
expect(document.body.getAttribute('style')).toBe('');
});

it('Lock multiple different target and container and unLock', () => {
const testContainer = document.createElement('div');

const locker1 = new ScrollLocker({
container: testContainer,
});
const locker2 = new ScrollLocker({
container: testContainer,
});

scrollLocker.lock();
locker1.lock();
locker2.lock();

expect(document.body.className).toBe(effectClassname);
expect(document.body.getAttribute('style')).toBe(effectStyle);
expect(testContainer.className).toBe(effectClassname);
expect(testContainer.getAttribute('style')).toBe(effectStyle);

locker1.unLock();

expect(document.body.className).toBe(effectClassname);
expect(document.body.getAttribute('style')).toBe(effectStyle);
expect(testContainer.className).toBe(effectClassname);
expect(testContainer.getAttribute('style')).toBe(effectStyle);

scrollLocker.unLock();

expect(document.body.className).toBe('');
expect(document.body.getAttribute('style')).toBe('');
expect(testContainer.className).toBe(effectClassname);
expect(testContainer.getAttribute('style')).toBe(effectStyle);

scrollLocker.unLock();
locker2.unLock();

expect(document.body.className).toBe('');
expect(document.body.getAttribute('style')).toBe('');
expect(testContainer.className).toBe('');
expect(testContainer.getAttribute('style')).toBe('');
});
});

0 comments on commit 54457b2

Please sign in to comment.