Slate: Autoscrolling not working

Created on 25 Jun 2020  Â·  6Comments  Â·  Source: ianstormtaylor/slate

Do you want to request a _feature_ or report a _bug_?

Bug

What's the current behavior?

Related to https://github.com/ianstormtaylor/slate/issues/3677 it seems that autoscrolling doesn't work anymore since https://github.com/ianstormtaylor/slate/commit/1652ecab52088e64ffbaa130fa4049f60cdce57e (in frames?).

Please see sandbox: https://jsfiddle.net/zobnxmtL/1/

Place the cursor at the end of the visible text and write so that the cursor leaves the visible area.

Jun-25-2020 11-05-38

Slate: 0.58.3
Browser: Chrome
OS: Mac

What's the expected behavior?

Automatic scrolling :)

bug ♥ help ⚑ cross platform

Most helpful comment

Auto scrolling does not work on the sample rich text editor https://www.slatejs.org/examples/richtext which is a typical use case where the scrollable container doesn't have a height set on it.

This scrollIntoView call works in Chrome for scrolling the element into view.

element.scrollIntoView({ behavior: "smooth", block: "nearest" }

This is a poor quality but working fix I made in case you need it before a fix goes into Slate proper:

      <Editable
        onSelect={(e) => {
          /**
           * Chrome doesn't scroll at bottom of the page. This fixes that.
           */
          if (!(window as any).chrome) return
          if (editor.selection == null) return
          try {
            /**
             * Need a try/catch because sometimes you get an error like:
             *
             * Error: Cannot resolve a DOM node from Slate node: {"type":"p","children":[{"text":"","by":-1,"at":-1}]}
             */
            const domPoint = ReactEditor.toDOMPoint(
              editor,
              editor.selection.focus
            )
            const node = domPoint[0]
            if (node == null) return
            const element = node.parentElement
            if (element == null) return
            element.scrollIntoView({ behavior: "smooth", block: "nearest" })
          } catch (e) {
            /**
             * Empty catch. Do nothing if there is an error.
             */
          }
        }}
      />

All 6 comments

I have the same issue and checked a few other browsers:

  • Works on Safari OSX
  • Works on Safari iOS
  • Scroll is broken on Chrome OSX
  • Scroll is broken on Firefox OSX

Works if you set the height on the Editable and then add a scroll:auto. Problem is if you want your scrollable container to be something separate from the Editor in which case I think https://github.com/ianstormtaylor/slate/commit/1652ecab52088e64ffbaa130fa4049f60cdce57e doesn't work.

Auto scrolling does not work on the sample rich text editor https://www.slatejs.org/examples/richtext which is a typical use case where the scrollable container doesn't have a height set on it.

This scrollIntoView call works in Chrome for scrolling the element into view.

element.scrollIntoView({ behavior: "smooth", block: "nearest" }

This is a poor quality but working fix I made in case you need it before a fix goes into Slate proper:

      <Editable
        onSelect={(e) => {
          /**
           * Chrome doesn't scroll at bottom of the page. This fixes that.
           */
          if (!(window as any).chrome) return
          if (editor.selection == null) return
          try {
            /**
             * Need a try/catch because sometimes you get an error like:
             *
             * Error: Cannot resolve a DOM node from Slate node: {"type":"p","children":[{"text":"","by":-1,"at":-1}]}
             */
            const domPoint = ReactEditor.toDOMPoint(
              editor,
              editor.selection.focus
            )
            const node = domPoint[0]
            if (node == null) return
            const element = node.parentElement
            if (element == null) return
            element.scrollIntoView({ behavior: "smooth", block: "nearest" })
          } catch (e) {
            /**
             * Empty catch. Do nothing if there is an error.
             */
          }
        }}
      />

@thesunny Thanks for the quick fix. For me it works for Firefox as well. So maybe include another check for Firefox
if (!(window.chrome || typeof InstallTrigger !== 'undefined')) return

Unfortunately it still not working for me if cursor is moving up by arrow keys. It only works scrolling down. Any idea on that?

I recently encountered this issue (along with #3463 and #3677), the fix from @thesunny (Thanks!) does scroll the element into view, but It centres it (which I understand that should be going to the start, center or end depending on the cursor position) as soon as it is modified.
eg. If there is a big paragraph and I enter a character anywhere on it, it centres the whole element, leaving the cursor out of the viewport.
https://codesandbox.io/s/slate-reproductions-forked-f6wnr

I had the same problem and created another workaround to make it work more as expected. Instead of getting the currently selected block, I convert the current selection into a DOMRect and insert a custom cursor element into the editor that is then scrolled into view. It requires that the editor is wrapped in an element with positon: relative.

This works a lot better in small editors where one block can span multiple lines and take up more than the full height as the cursor position is more precise.

Here's the code:

// I'm using these two helpers
import scrollIntoView from 'scroll-into-view-if-needed';
import { sleep } from 'sleepjs';

// This is assigned to the <Editable>'s onSelect
const onSelect = useCallback(async () => {
    // Only for Chrome
    if (!(window.chrome || typeof InstallTrigger !== 'undefined')) return;

    const insertOrUpdateCustomCursor = (rect: DOMRect) => {
        const editorNode = ReactEditor.toDOMNode(editor, editor);
        const editorRect = editorNode.getBoundingClientRect();
        const parentNode = editorNode.parentElement;

        if (!parentNode) return;

        let cursor = parentNode.querySelector<HTMLSpanElement>(
            '#custom-cursor'
        );

        if (!cursor) {
            cursor = document.createElement('span');
            cursor.id = 'custom-cursor';
            cursor.style.position = 'absolute';
            // for debugging purposes:
            cursor.style.border = '1px solid #f00';
            parentNode?.appendChild(cursor);
        }

        cursor.style.top = `${rect.y - editorRect.y}px`;
        cursor.style.left = `${rect.x - editorRect.x}px`;
        cursor.style.height = `${rect.height}px`;

        return cursor;
    };

    await sleep(10);

    try {
        const domSelection = window.getSelection();
        if (!domSelection || !ReactEditor.isFocused(editor)) {
            return;
        }

        const cursor = domSelection.getRangeAt(0).getBoundingClientRect();
        const customCursor = insertOrUpdateCustomCursor(cursor);

        if (customCursor) {
            scrollIntoView(customCursor, {
                scrollMode: 'if-needed',
                block: 'nearest',
            });
        }
    } catch (e) {
        console.warn('Error while scrolling to selection:', e);
    }
}, [editor]);
Was this page helpful?
0 / 5 - 0 ratings

Related issues

gorillatron picture gorillatron  Â·  3Comments

chriserickson picture chriserickson  Â·  3Comments

varoot picture varoot  Â·  3Comments

ianstormtaylor picture ianstormtaylor  Â·  3Comments

ianstormtaylor picture ianstormtaylor  Â·  3Comments