Skip to content

Instantly share code, notes, and snippets.

@phcorp
Last active February 3, 2024 18:46
Show Gist options
  • Select an option

  • Save phcorp/85e061e0fcd2755d8cda09565fc9fb51 to your computer and use it in GitHub Desktop.

Select an option

Save phcorp/85e061e0fcd2755d8cda09565fc9fb51 to your computer and use it in GitHub Desktop.

Revisions

  1. phcorp revised this gist Feb 3, 2024. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions dragToScroll.js
    Original file line number Diff line number Diff line change
    @@ -131,10 +131,10 @@ const dragToScroll = ({
    const style = document.createElement('style');
    style.innerHTML = `
    body {
    cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24' width='24'%3E%3Ccircle cx='12' cy='12' r='10' fill='rgba(0, 0, 0, .5)' stroke='rgba(255, 255, 255, .5)' stroke-width='2'%3E%3C/circle%3E%3C/svg%3E") 16 16, default;
    cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24' width='24'%3E%3Ccircle cx='12' cy='12' r='10' fill='rgba(0, 0, 0, .5)' stroke='rgba(255, 255, 255, .5)' stroke-width='2'%3E%3C/circle%3E%3C/svg%3E") 12 12, default;
    a {
    cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24' width='24'%3E%3Ccircle cx='12' cy='12' r='10' fill='rgba(0, 0, 0, .5)' stroke='rgba(255, 255, 255, .5)' stroke-width='2'%3E%3C/circle%3E%3C/svg%3E") 16 16, default;
    cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24' width='24'%3E%3Ccircle cx='12' cy='12' r='10' fill='rgba(0, 0, 0, .5)' stroke='rgba(255, 255, 255, .5)' stroke-width='2'%3E%3C/circle%3E%3C/svg%3E") 12 12, default;
    }
    }
    `;
  2. phcorp revised this gist Feb 3, 2024. 1 changed file with 13 additions and 2 deletions.
    15 changes: 13 additions & 2 deletions dragToScroll.js
    Original file line number Diff line number Diff line change
    @@ -1,13 +1,20 @@
    const IGNORED_SELECTOR = 'input, label, select, textarea, button, fieldset, legend, datalist, output, option, optgroup';
    const SCROLLABLE_SELECTOR = '.drag-to-scroll';

    /**
    * Transform mouse drag gesture to scroll.
    *
    * @param {string} [selector] - The scrollable element selector
    * @param {{
    * [ignoredSelector]: string, // The ignored elements selector
    * [selector]: string, // The scrollable elements selector
    * }}
    *
    * @return {(function(): void)|*} - Uninstall function
    */
    const dragToScroll = (selector = SCROLLABLE_SELECTOR) => {
    const dragToScroll = ({
    ignoredSelector = IGNORED_SELECTOR,
    selector = SCROLLABLE_SELECTOR,
    }) => {
    /**
    * Returns the scrollable element from a child element.
    *
    @@ -16,6 +23,10 @@ const dragToScroll = (selector = SCROLLABLE_SELECTOR) => {
    * @return {null|HTMLElement} - The element
    */
    const getScrollableElement = (element) => {
    if (element?.matches(ignoredSelector) || element?.closest(ignoredSelector)) {
    return null;
    }

    if (element?.matches(selector)) {
    return element;
    }
  3. phcorp revised this gist Feb 3, 2024. 1 changed file with 0 additions and 6 deletions.
    6 changes: 0 additions & 6 deletions dragToScroll.js
    Original file line number Diff line number Diff line change
    @@ -89,8 +89,6 @@ const dragToScroll = (selector = SCROLLABLE_SELECTOR) => {
    };
    const mouseleaveListener = (event) => {
    if(event.clientY <= 0 || event.clientX <= 0 || (event.clientX >= window.innerWidth || event.clientY >= window.innerHeight)) {
    const clientX = Math.max(0, Math.min(window.innerWidth - 1, event.clientX));
    const clientY = Math.max(0, Math.min(window.innerHeight - 1, event.clientY));
    const pageX = Math.max(0, Math.min(window.innerWidth - 1, event.pageX));
    const pageY = Math.max(0, Math.min(window.innerHeight - 1, event.pageY));
    const scrollableElement = getScrollableElement(document.elementFromPoint(pageX, pageY));
    @@ -100,10 +98,6 @@ const dragToScroll = (selector = SCROLLABLE_SELECTOR) => {
    mouseupListener(new CustomEvent('mouseup', {
    bubbles: true,
    detail: {
    clientX,
    clientY,
    pageX,
    pageY,
    target: scrollableElement,
    },
    }));
  4. phcorp created this gist Feb 3, 2024.
    162 changes: 162 additions & 0 deletions dragToScroll.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,162 @@
    const SCROLLABLE_SELECTOR = '.drag-to-scroll';

    /**
    * Transform mouse drag gesture to scroll.
    *
    * @param {string} [selector] - The scrollable element selector
    *
    * @return {(function(): void)|*} - Uninstall function
    */
    const dragToScroll = (selector = SCROLLABLE_SELECTOR) => {
    /**
    * Returns the scrollable element from a child element.
    *
    * @param {HTMLElement|null} element - The child element
    *
    * @return {null|HTMLElement} - The element
    */
    const getScrollableElement = (element) => {
    if (element?.matches(selector)) {
    return element;
    }

    const parentScrollableElement = element?.closest(selector);
    if (parentScrollableElement) {
    return parentScrollableElement;
    }

    return null;
    };

    const mouseupListener = (event) => {
    const scrollableElement = getScrollableElement(event.target || event.detail.target);
    if (!scrollableElement ||scrollableElement.dataset.dragging !== 'true') {
    return;
    }
    scrollableElement.dataset.dragging = 'false';
    scrollableElement.dispatchEvent(new CustomEvent('scrollend', {
    detail: {
    target: scrollableElement,
    },
    }));
    event.preventDefault();
    event.stopPropagation();
    };
    const mousedownListener = (event) => {
    const scrollableElement = getScrollableElement(event.target);
    if (!scrollableElement || getScrollableElement(document.elementFromPoint(event.pageX, event.pageY)) !== scrollableElement) {
    return;
    }
    scrollableElement.dataset.dragging = 'true';
    scrollableElement.dataset.draggingLastClientX = `${event.clientX}`;
    scrollableElement.dataset.draggingLastClienty = `${event.clientY}`;
    scrollableElement.addEventListener('mouseleave', (event) => {
    mouseupListener(new CustomEvent('mouseup', {
    bubbles: true,
    detail: {
    target: scrollableElement,
    },
    }));
    }, {
    capture: false,
    once: true,
    });
    event.preventDefault();
    event.stopPropagation();
    };
    const mousemoveListener = (event) => {
    const scrollableElement = getScrollableElement(event.target);
    if (!scrollableElement || scrollableElement.dataset.dragging !== 'true') {
    return;
    }
    const lastClientX = parseInt(scrollableElement.dataset.draggingLastClientX, 10);
    const lastClientY = parseInt(scrollableElement.dataset.draggingLastClienty, 10);
    const clientX = event.clientX - lastClientX;
    const clientY = event.clientY - lastClientY;
    scrollableElement.scrollLeft -= clientX;
    scrollableElement.scrollTop -= clientY;
    scrollableElement.dataset.draggingLastClientX = `${event.clientX}`;
    scrollableElement.dataset.draggingLastClienty = `${event.clientY}`;
    if (scrollableElement === document.body) {
    document.documentElement.scrollLeft -= clientX;
    document.documentElement.scrollTop -= clientY;
    }
    scrollableElement.dispatchEvent(new CustomEvent('scroll', {
    detail: {
    target: scrollableElement,
    },
    }));
    };
    const mouseleaveListener = (event) => {
    if(event.clientY <= 0 || event.clientX <= 0 || (event.clientX >= window.innerWidth || event.clientY >= window.innerHeight)) {
    const clientX = Math.max(0, Math.min(window.innerWidth - 1, event.clientX));
    const clientY = Math.max(0, Math.min(window.innerHeight - 1, event.clientY));
    const pageX = Math.max(0, Math.min(window.innerWidth - 1, event.pageX));
    const pageY = Math.max(0, Math.min(window.innerHeight - 1, event.pageY));
    const scrollableElement = getScrollableElement(document.elementFromPoint(pageX, pageY));
    if (!scrollableElement) {
    return;
    }
    mouseupListener(new CustomEvent('mouseup', {
    bubbles: true,
    detail: {
    clientX,
    clientY,
    pageX,
    pageY,
    target: scrollableElement,
    },
    }));
    }
    };

    document.addEventListener('mouseleave', mouseleaveListener, {
    capture: false,
    });
    document.addEventListener('mouseup', mouseupListener, {
    capture: true,
    });
    document.addEventListener('mousemove', mousemoveListener, {
    capture: false,
    });
    document.addEventListener('mousedown', mousedownListener, {
    capture: true,
    });

    const style = document.createElement('style');
    style.innerHTML = `
    body {
    cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24' width='24'%3E%3Ccircle cx='12' cy='12' r='10' fill='rgba(0, 0, 0, .5)' stroke='rgba(255, 255, 255, .5)' stroke-width='2'%3E%3C/circle%3E%3C/svg%3E") 16 16, default;
    a {
    cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24' width='24'%3E%3Ccircle cx='12' cy='12' r='10' fill='rgba(0, 0, 0, .5)' stroke='rgba(255, 255, 255, .5)' stroke-width='2'%3E%3C/circle%3E%3C/svg%3E") 16 16, default;
    }
    }
    `;
    document.body.appendChild(style);

    return () => {
    document.removeEventListener('mousedown', mousedownListener, {
    capture: true,
    });
    document.removeEventListener('mousemove', mousemoveListener, {
    capture: false,
    });
    document.removeEventListener('mouseup', mouseupListener, {
    capture: true,
    });
    document.removeEventListener('mouseleave', mouseleaveListener, {
    capture: false,
    });

    document.body.removeChild(style);

    document.querySelectorAll(selector).forEach((scrollableElement) => {
    delete scrollableElement.dataset.dragging;
    delete scrollableElement.dataset.draggingLastClientX;
    delete scrollableElement.dataset.draggingLastClienty;
    });
    };
    };

    export default dragToScroll;