aboutsummaryrefslogblamecommitdiff
path: root/src/content/scrolls.js
blob: f3124a14a446066e8d8ed354766727d09366d737 (plain) (tree)
1
2
3
4
5
6
7
8
9
                                         
                          
 
                                                 
                         
 
                                                                  



                                                            






                                                                              
                                                            

                   
                                                                     
                               
                                           





                        

                                                      
   
                                           
   
                                                               

                  
                                         
  


                              
                                
                           
                         
   
 
                  


                                  
                           
              
                         
       


                        

                                           

                  


                                  
     
                                                    
   
 
 







                                
 



                                                       
                                             
                              
                                     
                  
                                       
   
                                                  
  
                                              
                              
                                     
                  
                                       
   
                                                  
  
                                        
                              
                                   
                             
                  
                           
   
                                                  
  
                                    
                                              

                                 
                              
                            
            
                                              
  
                                    
                              
                              
                                              
  
                                  
                              
            
                           
                                              
  
                                 
                              
                             
                           
                                              
  
        
            
                                                   
                                                        
  
import * as doms from 'shared/utils/dom';

const SCROLL_DELTA_X = 64;
const SCROLL_DELTA_Y = 64;

// dirty way to store scrolling state on globally
let scrolling = false;
let lastTimeoutId = null;

const isScrollableStyle = (element) => {
  let { overflowX, overflowY } = window.getComputedStyle(element);
  return !(overflowX !== 'scroll' && overflowX !== 'auto' &&
    overflowY !== 'scroll' && overflowY !== 'auto');
};

const isOverflowed = (element) => {
  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.
const findScrollable = (element) => {
  if (isScrollableStyle(element) && isOverflowed(element)) {
    return element;
  }

  let children = Array.from(element.children).filter(doms.isVisible);
  for (let child of children) {
    let scrollable = findScrollable(child);
    if (scrollable) {
      return scrollable;
    }
  }
  return null;
};

const scrollTarget = () => {
  if (isOverflowed(window.document.documentElement)) {
    return window.document.documentElement;
  }
  if (isOverflowed(window.document.body)) {
    return window.document.body;
  }
  let target = findScrollable(window.document.documentElement);
  if (target) {
    return target;
  }
  return window.document.documentElement;
};

const resetScrolling = () => {
  scrolling = false;
};

class Scroller {
  constructor(element, smooth) {
    this.element = element;
    this.smooth = smooth;
  }

  scrollTo(x, y) {
    if (!this.smooth) {
      this.element.scrollTo(x, y);
      return;
    }
    this.element.scrollTo({
      left: x,
      top: y,
      behavior: 'smooth',
    });
    this.prepareReset();
  }

  scrollBy(x, y) {
    let left = this.element.scrollLeft + x;
    let top = this.element.scrollTop + y;
    this.scrollTo(left, top);
  }

  prepareReset() {
    scrolling = true;
    if (lastTimeoutId) {
      clearTimeout(lastTimeoutId);
      lastTimeoutId = null;
    }
    lastTimeoutId = setTimeout(resetScrolling, 100);
  }
}

class RoughtScroller {
  constructor(element) {
    this.element = element;
  }

  scroll(x, y) {
    this.element.scrollTo(x, y);
  }
}

const getScroll = () => {
  let target = scrollTarget();
  return { x: target.scrollLeft, y: target.scrollTop };
};

const scrollVertically = (count, smooth) => {
  let target = scrollTarget();
  let delta = SCROLL_DELTA_Y * count;
  if (scrolling) {
    delta = SCROLL_DELTA_Y * count * 4;
  }
  new Scroller(target, smooth).scrollBy(0, delta);
};

const scrollHorizonally = (count, smooth) => {
  let target = scrollTarget();
  let delta = SCROLL_DELTA_X * count;
  if (scrolling) {
    delta = SCROLL_DELTA_X * count * 4;
  }
  new Scroller(target, smooth).scrollBy(delta, 0);
};

const scrollPages = (count, smooth) => {
  let target = scrollTarget();
  let height = target.clientHeight;
  let delta = height * count;
  if (scrolling) {
    delta = height * count;
  }
  new Scroller(target, smooth).scrollBy(0, delta);
};

const scrollTo = (x, y, smooth) => {
  let target = scrollTarget();
  new Scroller(target, smooth).scrollTo(x, y);
};

const scrollToTop = (smooth) => {
  let target = scrollTarget();
  let x = target.scrollLeft;
  let y = 0;
  new Scroller(target, smooth).scrollTo(x, y);
};

const scrollToBottom = (smooth) => {
  let target = scrollTarget();
  let x = target.scrollLeft;
  let y = target.scrollHeight;
  new Scroller(target, smooth).scrollTo(x, y);
};

const scrollToHome = (smooth) => {
  let target = scrollTarget();
  let x = 0;
  let y = target.scrollTop;
  new Scroller(target, smooth).scrollTo(x, y);
};

const scrollToEnd = (smooth) => {
  let target = scrollTarget();
  let x = target.scrollWidth;
  let y = target.scrollTop;
  new Scroller(target, smooth).scrollTo(x, y);
};

export {
  getScroll,
  scrollVertically, scrollHorizonally, scrollPages,
  scrollTo,
  scrollToTop, scrollToBottom, scrollToHome, scrollToEnd
};