A fork of https://github.com/ueokande/vim-vixen
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
180 lines
4.7 KiB
180 lines
4.7 KiB
5 years ago
|
import * as doms from '../../shared/utils/dom';
|
||
7 years ago
|
|
||
5 years ago
|
const SCROLL_DELTA_X = 64;
|
||
|
const SCROLL_DELTA_Y = 64;
|
||
7 years ago
|
|
||
7 years ago
|
// dirty way to store scrolling state on globally
|
||
5 years ago
|
let scrolling = false;
|
||
5 years ago
|
let lastTimeoutId: number | null = null;
|
||
7 years ago
|
|
||
5 years ago
|
const isScrollableStyle = (element: Element): boolean => {
|
||
7 years ago
|
let { overflowX, overflowY } = window.getComputedStyle(element);
|
||
7 years ago
|
return !(overflowX !== 'scroll' && overflowX !== 'auto' &&
|
||
|
overflowY !== 'scroll' && overflowY !== 'auto');
|
||
|
};
|
||
|
|
||
5 years ago
|
const isOverflowed = (element: Element): boolean => {
|
||
7 years ago
|
return element.scrollWidth > element.clientWidth ||
|
||
|
element.scrollHeight > element.clientHeight;
|
||
|
};
|
||
|
|
||
|
// Find a visiable and scrollable element by depth-first search. Currently
|
||
|
// this method is called by each scrolling, and the returned value of this
|
||
|
// method is not cached. That does not cause performance issue because in the
|
||
|
// most pages, the window is root element i,e, documentElement.
|
||
5 years ago
|
const findScrollable = (element: Element): Element | null => {
|
||
7 years ago
|
if (isScrollableStyle(element) && isOverflowed(element)) {
|
||
7 years ago
|
return element;
|
||
|
}
|
||
|
|
||
7 years ago
|
let children = Array.from(element.children).filter(doms.isVisible);
|
||
7 years ago
|
for (let child of children) {
|
||
7 years ago
|
let scrollable = findScrollable(child);
|
||
7 years ago
|
if (scrollable) {
|
||
|
return scrollable;
|
||
|
}
|
||
|
}
|
||
|
return null;
|
||
|
};
|
||
|
|
||
7 years ago
|
const scrollTarget = () => {
|
||
|
if (isOverflowed(window.document.documentElement)) {
|
||
|
return window.document.documentElement;
|
||
7 years ago
|
}
|
||
7 years ago
|
if (isOverflowed(window.document.body)) {
|
||
|
return window.document.body;
|
||
7 years ago
|
}
|
||
7 years ago
|
let target = findScrollable(window.document.documentElement);
|
||
7 years ago
|
if (target) {
|
||
|
return target;
|
||
|
}
|
||
7 years ago
|
return window.document.documentElement;
|
||
7 years ago
|
};
|
||
|
|
||
5 years ago
|
const resetScrolling = () => {
|
||
|
scrolling = false;
|
||
|
};
|
||
|
|
||
5 years ago
|
class Scroller {
|
||
5 years ago
|
private element: Element;
|
||
|
|
||
|
private smooth: boolean;
|
||
|
|
||
|
constructor(element: Element, smooth: boolean) {
|
||
7 years ago
|
this.element = element;
|
||
5 years ago
|
this.smooth = smooth;
|
||
7 years ago
|
}
|
||
|
|
||
5 years ago
|
scrollTo(x: number, y: number): void {
|
||
5 years ago
|
if (!this.smooth) {
|
||
|
this.element.scrollTo(x, y);
|
||
|
return;
|
||
|
}
|
||
5 years ago
|
this.element.scrollTo({
|
||
5 years ago
|
left: x,
|
||
|
top: y,
|
||
5 years ago
|
behavior: 'smooth',
|
||
5 years ago
|
});
|
||
5 years ago
|
this.prepareReset();
|
||
|
}
|
||
|
|
||
5 years ago
|
scrollBy(x: number, y: number): void {
|
||
5 years ago
|
let left = this.element.scrollLeft + x;
|
||
|
let top = this.element.scrollTop + y;
|
||
|
this.scrollTo(left, top);
|
||
5 years ago
|
}
|
||
|
|
||
5 years ago
|
prepareReset(): void {
|
||
5 years ago
|
scrolling = true;
|
||
|
if (lastTimeoutId) {
|
||
|
clearTimeout(lastTimeoutId);
|
||
|
lastTimeoutId = null;
|
||
7 years ago
|
}
|
||
5 years ago
|
lastTimeoutId = setTimeout(resetScrolling, 100);
|
||
7 years ago
|
}
|
||
7 years ago
|
}
|
||
7 years ago
|
|
||
5 years ago
|
type Point = { x: number, y: number };
|
||
|
|
||
|
export default interface ScrollPresenter {
|
||
|
getScroll(): Point;
|
||
|
scrollVertically(amount: number, smooth: boolean): void;
|
||
|
scrollHorizonally(amount: number, smooth: boolean): void;
|
||
|
scrollPages(amount: number, smooth: boolean): void;
|
||
|
scrollTo(x: number, y: number, smooth: boolean): void;
|
||
|
scrollToTop(smooth: boolean): void;
|
||
|
scrollToBottom(smooth: boolean): void;
|
||
|
scrollToHome(smooth: boolean): void;
|
||
|
scrollToEnd(smooth: boolean): void;
|
||
|
|
||
|
// eslint-disable-next-line semi
|
||
|
}
|
||
6 years ago
|
|
||
5 years ago
|
export class ScrollPresenterImpl {
|
||
|
getScroll(): Point {
|
||
|
let target = scrollTarget();
|
||
|
return { x: target.scrollLeft, y: target.scrollTop };
|
||
7 years ago
|
}
|
||
7 years ago
|
|
||
5 years ago
|
scrollVertically(count: number, smooth: boolean): void {
|
||
|
let target = scrollTarget();
|
||
|
let delta = SCROLL_DELTA_Y * count;
|
||
|
if (scrolling) {
|
||
|
delta = SCROLL_DELTA_Y * count * 4;
|
||
|
}
|
||
|
new Scroller(target, smooth).scrollBy(0, delta);
|
||
7 years ago
|
}
|
||
7 years ago
|
|
||
5 years ago
|
scrollHorizonally(count: number, smooth: boolean): void {
|
||
|
let target = scrollTarget();
|
||
|
let delta = SCROLL_DELTA_X * count;
|
||
|
if (scrolling) {
|
||
|
delta = SCROLL_DELTA_X * count * 4;
|
||
|
}
|
||
|
new Scroller(target, smooth).scrollBy(delta, 0);
|
||
5 years ago
|
}
|
||
7 years ago
|
|
||
5 years ago
|
scrollPages(count: number, smooth: boolean): void {
|
||
|
let target = scrollTarget();
|
||
|
let height = target.clientHeight;
|
||
|
let delta = height * count;
|
||
|
if (scrolling) {
|
||
|
delta = height * count;
|
||
|
}
|
||
|
new Scroller(target, smooth).scrollBy(0, delta);
|
||
|
}
|
||
6 years ago
|
|
||
5 years ago
|
scrollTo(x: number, y: number, smooth: boolean): void {
|
||
|
let target = scrollTarget();
|
||
|
new Scroller(target, smooth).scrollTo(x, y);
|
||
|
}
|
||
7 years ago
|
|
||
5 years ago
|
scrollToTop(smooth: boolean): void {
|
||
|
let target = scrollTarget();
|
||
|
let x = target.scrollLeft;
|
||
|
let y = 0;
|
||
|
new Scroller(target, smooth).scrollTo(x, y);
|
||
|
}
|
||
7 years ago
|
|
||
5 years ago
|
scrollToBottom(smooth: boolean): void {
|
||
|
let target = scrollTarget();
|
||
|
let x = target.scrollLeft;
|
||
|
let y = target.scrollHeight;
|
||
|
new Scroller(target, smooth).scrollTo(x, y);
|
||
|
}
|
||
7 years ago
|
|
||
5 years ago
|
scrollToHome(smooth: boolean): void {
|
||
|
let target = scrollTarget();
|
||
|
let x = 0;
|
||
|
let y = target.scrollTop;
|
||
|
new Scroller(target, smooth).scrollTo(x, y);
|
||
|
}
|
||
7 years ago
|
|
||
5 years ago
|
scrollToEnd(smooth: boolean): void {
|
||
|
let target = scrollTarget();
|
||
|
let x = target.scrollWidth;
|
||
|
let y = target.scrollTop;
|
||
|
new Scroller(target, smooth).scrollTo(x, y);
|
||
|
}
|
||
|
}
|