smooth-scrollbar
Version:
Customize scrollbar in modern browsers with smooth scrolling experience.
119 lines (91 loc) • 2.86 kB
text/typescript
import clamp from 'lodash.clamp';
import * as I from '../interfaces/';
import {
eventScope,
getPosition,
} from '../utils/';
export function selectHandler(scrollbar: I.Scrollbar) {
const addEvent = eventScope(scrollbar);
const { containerEl, contentEl } = scrollbar;
let isSelected = false;
let isContextMenuOpened = false; // flag to prevent selection when context menu is opened
let animationID: number;
function scroll({ x, y }) {
if (!x && !y) return;
const { offset, limit } = scrollbar;
// DISALLOW delta transformation
scrollbar.setMomentum(
clamp(offset.x + x, 0, limit.x) - offset.x,
clamp(offset.y + y, 0, limit.y) - offset.y,
);
animationID = requestAnimationFrame(() => {
scroll({ x, y });
});
}
addEvent(window, 'mousemove', (evt: MouseEvent) => {
if (!isSelected) return;
cancelAnimationFrame(animationID);
const dir = calcMomentum(scrollbar, evt);
scroll(dir);
});
// prevent scrolling when context menu is opened
// NOTE: `contextmenu` event may be fired
// 1. BEFORE `selectstart`: when user right-clicks on the text content -> prevent future scrolling,
// 2. AFTER `selectstart`: when user right-clicks on the blank area -> cancel current scrolling,
// so we need to both set the flag and cancel current scrolling
addEvent(contentEl, 'contextmenu', () => {
// set the flag to prevent future scrolling
isContextMenuOpened = true;
// stop current scrolling
cancelAnimationFrame(animationID);
isSelected = false;
});
// reset context menu flag on mouse down
// to ensure the scrolling is allowed in the next selection
addEvent(contentEl, 'mousedown', () => {
isContextMenuOpened = false;
});
addEvent(contentEl, 'selectstart', () => {
if (isContextMenuOpened) {
return;
}
cancelAnimationFrame(animationID);
isSelected = true;
});
addEvent(window, 'mouseup blur', () => {
cancelAnimationFrame(animationID);
isSelected = false;
isContextMenuOpened = false;
});
// patch for touch devices
addEvent(containerEl, 'scroll', (evt: Event) => {
evt.preventDefault();
containerEl.scrollTop = containerEl.scrollLeft = 0;
});
}
function calcMomentum(
scrollbar: I.Scrollbar,
evt: MouseEvent,
) {
const { top, right, bottom, left } = scrollbar.bounding;
const { x, y } = getPosition(evt);
const res = {
x: 0,
y: 0,
};
const padding = 20;
if (x === 0 && y === 0) return res;
if (x > right - padding) {
res.x = (x - right + padding);
} else if (x < left + padding) {
res.x = (x - left - padding);
}
if (y > bottom - padding) {
res.y = (y - bottom + padding);
} else if (y < top + padding) {
res.y = (y - top - padding);
}
res.x *= 2;
res.y *= 2;
return res;
}