raingart / Hotkeys Page Scroll

// ==UserScript==
// @name         Hotkeys Page Scroll
// @namespace    hotkey-scroll-page
// @version      0.1
// @description  Scroll half a page up and down when pressing the hotkeykeys
// @author       raingart
// @license      Apache-2.0
// @include      *://*/*
// @run-at       document-end
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_registerMenuCommand
// @require      https://cdn.jsdelivr.net/gh/kufii/My-UserScripts@c7f613292672252995cb02a0cab3b6acb18ccac5/libs/gm_config.js
// ==/UserScript==
/*jshint esversion: 6 */


(function() {

   const keysList = ['none','KeyA','KeyB','KeyC','KeyD','KeyE','KeyF','KeyG','KeyH','KeyI','KeyJ','KeyK','KeyL','KeyM','KeyN','KeyO','KeyP','KeyQ','KeyR','KeyS','KeyT','KeyU','KeyV','KeyW','KeyX','KeyY','KeyZ','[',']','+','-',',','.','/','<',';','\\','ArrowUp','ArrowDown','ArrowLeft','ArrowRight','ShiftLeft','ShiftRight','ControlLeft','ControlRight','AltLeft','AltRight',0,1,2,3,4,5,6,7,8,9];

   const config = GM_config([
      {
         key: 'scroll-up',
         label: 'Scroll up',
         default: 'KeyW',
         type: 'dropdown',
         values: keysList,
      },
      {
         key: 'scroll-down',
         label: 'Scroll down',
         default: 'KeyS',
         type: 'dropdown',
         values: keysList,
      },
      {
         key: 'scroll-half-up',
         label: 'Scroll-half up',
         default: 'KeyE',
         type: 'dropdown',
         values: keysList,
      },
      {
         key: 'scroll-half-down',
         label: 'Scroll-half down',
         default: 'KeyD',
         type: 'dropdown',
         values: keysList,
      },
      {
         key: 'scroll-duration',
         label: 'Default scroll duration (ms)',
         default: 150,
         min: 50,
         max: 500,
         step: 10,
         type: 'number',
      },
      {
         key: 'scroll-distance',
         label: 'Default scroll distance (px)',
         default: 30,
         min: 5,
         max: 100,
         step: 1,
         type: 'number',
      },
   ]);
   const conf = config.load();
   config.onsave = cfg => (conf = cfg);
   GM_registerMenuCommand('Options', config.setup);

   document.addEventListener('keydown', evt => {
      // Check for modifier keys being pressed
      if (evt.ctrlKey || evt.altKey || evt.metaKey || evt.shiftKey) return;
      if (['input', 'textarea', 'select'].includes(evt.target.localName) || evt.target.isContentEditable) return;

      const targetScrollPosition = determineTargetScrollPosition(evt.code)

      if (+targetScrollPosition) {
         evt.preventDefault();
         evt.stopPropagation();
         evt.stopImmediatePropagation();

         startScrolling(targetScrollPosition);
      }

      function startScrolling(targetScrollPosition) {
         let startTime;
         let scrollInterval = requestAnimationFrame(smoothScroll);

         function smoothScroll(currentTime = performance.now()) {
            if (evt.repeat) cancelAnimationFrame(scrollInterval);
            if (!startTime) startTime = currentTime;

            const scrolledProgress = Math.min((currentTime - startTime) / +conf['scroll-duration'], 1);
            const distance = (targetScrollPosition - window.pageYOffset) * scrolledProgress;

            scrollApply({ 'distance': distance });

            if (scrolledProgress < 1) requestAnimationFrame(smoothScroll);
            else cancelAnimationFrame(scrollInterval);
         }

         function scrollApply({ distance, direction = 'vertical' }) {
            if (direction !== 'vertical' && direction !== 'horizontal') {
               throw new Error('Invalid scroll direction. Use "vertical" or "horizontal".');
            }
            switch (direction) {
               case 'vertical':
                  window.scrollBy(0, distance);
                  break;
               case 'horizontal':
                  window.scrollBy(distance, 0);
                  break;
            }
         }
      }

      function determineTargetScrollPosition(keyCode) {
         let targetScrollPosition = 0;

         switch (keyCode) {
            case conf['scroll-up']:
               targetScrollPosition = window.pageYOffset - +conf['scroll-distance'];
               break;
            case conf['scroll-down']:
               targetScrollPosition = window.pageYOffset + +conf['scroll-distance'];
               break;
            case conf['scroll-half-up']:
               targetScrollPosition = window.pageYOffset - window.innerHeight / 2;
               break;
            case conf['scroll-half-down']:
               targetScrollPosition = Math.min(window.pageYOffset + window.innerHeight / 2, document.body.scrollHeight);
               break;
            // Add cases for other key presses if needed
         }

         return targetScrollPosition;
      }

   }, { capture: true });

})();