slab / quill

Quill is a modern WYSIWYG editor built for compatibility and extensibility
https://quilljs.com
BSD 3-Clause "New" or "Revised" License
43.72k stars 3.4k forks source link

Pasting in content causes scroll to "jump" before going back to original position #1374

Closed sferoze closed 7 months ago

sferoze commented 7 years ago

The Quill editor I am using auto grows as the user types. I also have multiple notes (Quill editors) on the screen at a time. I am using the scrollingContainer option correctly.

After pasting in content, Quill does resume the original scroll position but only after the screen flickers a bit. (This is because the scroll jumps to another location and then it set back right away, which causes the screen to "jump" or "flicker")

On mobile it is even worse, as the jump it very noticeable.

So the original scroll position is resumed but it looks very janky. Users will feel like the app is not solid with this side effect. Any advice on how to prevent this?

Steps for Reproduction

  1. Visit the quill demo page with auto grow and scrolling container
  2. Try pasting in content into the middle of the document. Try both on desktop and mobile
  3. You will notice the scroll area jump or flicker but then go back to it's original position.

Expected behavior:

When pasting in content, there should be no flicker, jumping, flashes or what not. It should be stable and just accept the content like pasting in Word, or any other text editing program.

Actual behavior:

The screen jumps or flashes when pasting in content, but after it resumes its original scroll position. The issue here is the flash or jump which makes the app feel janky.

Platforms:

Chrome and Safari

Version:

1.2.2

hanzhaodeng commented 5 years ago

Will the solution from @ArsalanSavand get integrated? It works for me so far.

gfyoung commented 5 years ago

The CSS-only solutions seem like the most robust and least invasive ones to me. JavaScript-based solutions could have unexpected side effects. 🤷‍♂

I had to augment the suggestion here and ended up getting this:

.ql-clipboard {
   position: fixed;
   display: none;

   left: 50%;
   top: 50%;
}

The idea is that by placing the clipboard smack in the middle, it will always be "in view" when you focus on it. The display attribute was needed to prevent flickering during paste.

Pure CSS, no JavaScript needed.

gigaj0ule commented 5 years ago

@gfyoung if you do that, then the selection range is wrong. Chrome needs to have a visible element to select a range.

BUT, if you do this, it works perfectly.

.ql-clipboard {
   position: fixed !important;
   opacity: 0 !important;
   left: 50% !important;
   top: 50% !important;
}
ArsalanSavand commented 5 years ago

This is the final fix which is inherited from the project's code itself.

Be sure to set the scrollingContainer to element that is responsible for scrolling the view. For example, let's say body element is responsible for scrolling the view, then we will have: scrollingContainer: html, body.

But sometimes body element is not responsible for scrolling the view, let's say we have an element around our Quill editor or anywhere inside of body element and it has a class of .editor-scroller, then we will have: scrollingContainer: .editor-scroller

import Quill, { RangeStatic, StringMap } from 'quill';
import Delta from 'quill-delta';

const ClipboardModule: any = Quill.import('modules/clipboard');

class CustomClipboard extends ClipboardModule {
  onPaste(event: ClipboardEvent): void {
    const range: RangeStatic = this.quill.getSelection();
    const formats: StringMap = this.quill.getFormat(range.index);
    const pastedDelta: Delta = this.convert(formats);
    const delta: Delta = new Delta()
      .retain(range.index)
      .delete(range.length)
      .concat(pastedDelta);
    this.quill.updateContents(delta, 'silent');
    // range.length contributes to delta.length()
    this.quill.setSelection(delta.length() - range.length, range.length, 'silent');
    this.quill.scrollIntoView();
  }
}

Quill.register('modules/clipboard', CustomClipboard, true);

The above code has been written in a TypeScript file, if you have a JavaScript file just remove type declarations.

For those who are going to write this in a TypeScript file, I used this library for Quill types: https://www.npmjs.com/package/@types/quill

Chris-Kin commented 5 years ago

For me all problems with scroll jumps were gone, after I had set scrollingContainer: document.documentElement

It works for me, but there is still a flicker when paste.

Chris-Kin commented 5 years ago

For anyone interested in a work around this issue, I override the Clipboard module to accept only plain text. I guess that is still better in term of UX rather than letting the screen jump. Some code is below:

import Quill from 'quill'
import Delta from 'quill-delta'

const Clipboard = Quill.import('modules/clipboard')

class PlainTextClipboard extends Clipboard {
  onPaste (e) {
    if (e.defaultPrevented || !this.quill.isEnabled()) return
    let range = this.quill.getSelection()
    let delta = new Delta().retain(range.index)

    if (e && e.clipboardData && e.clipboardData.types && e.clipboardData.getData) {
      let text = (e.originalEvent || e).clipboardData.getData('text/plain')
      let cleanedText = this.convert(text)

      // Stop the data from actually being pasted
      e.stopPropagation()
      e.preventDefault()

      // Process cleaned text
      delta = delta.concat(cleanedText).delete(range.length)
      this.quill.updateContents(delta, Quill.sources.USER)
      // range.length contributes to delta.length()
      this.quill.setSelection(delta.length() - range.length, Quill.sources.SILENT)

      return false
    }
  }
}

Quill.register('modules/clipboard', PlainTextClipboard)

It prevents user from pasting images, so if you want to paste img into quill, this dose not work.

njleonzhang commented 5 years ago

The original onPaste doesn't call clipboardData to read user copied content, How can it work?

gigaj0ule commented 5 years ago

Does the CSS in https://github.com/quilljs/quill/issues/1374#issuecomment-534410373 work for you two?

Chris-Kin commented 5 years ago

Does the CSS in #1374 (comment) work for you two?

doesn't work for me.

itsmhuang commented 5 years ago

I set scrollingContainer to html and add this css:

.ql-clipboard {
  position: fixed;
}

it fixed the problem for me. try it out.

huanlirui commented 5 years ago

现在我解决了这个问题: 首先,设置 scrollingContainer="your scroll DIV DOM" 然后设置CSS .ql-clipboard { position: fixed !important; left: 50% !important; top: 50% !important;
}

Now I've solved the problem: First, set scrollingcontainer = "your scroll div DOM" Then set up CSS .ql-clipboard { position: fixed !important; left: 50% !important; top: 50% !important;
} try it

Cobatkao commented 3 years ago

@attacking do as you mentioned above, it really works! many thanks!

fengchangfight commented 3 years ago

this is how i fixed my problem: before: <vue-editor @text-change="autosave" :editorToolbar="customToolbar" v-model="memDetail.content"

after: <quill-editor @change="autosave" style="height: 480px" v-model="memDetail.content" :content="memDetail.content" />

I don't know why, but seems like a css problem

Chris-Kin commented 3 years ago

@attacking do as you mentioned above, it really works! many thanks!

Glad to help you

quroom commented 3 years ago

this is how i fixed my problem: before: <vue-editor @text-change="autosave" :editorToolbar="customToolbar" v-model="memDetail.content"

after: <quill-editor @change="autosave" style="height: 480px" v-model="memDetail.content" :content="memDetail.content" />

I don't know why, but seems like a css problem

@change will make error. So scroll to top was not run in clipboard paste event. If you have text style, It will be ignored.

er-nabin-bhusal commented 3 years ago

Following is my solution. scrollingContainer="html, body"

.ql-clipboard {
    position: fixed;
    display: none;
    left: 50%;
    top: 50%;
  }
sidraw27 commented 3 years ago

All you need to do was unset the outside container's overflow and change your container height.

.quillWrapper {
  overflow: unset !important;
  height: 500px !important;
}
.ql-container {
  height: 450px !important;
}
alejoacevedor1 commented 3 years ago

In my case the editor is inside a different scrolling container, not 'html, body', which is why the above-mentioned fixes didn't exactly suit.

This simple workaround though did the job perfectly:

.ql-clipboard {
    position: fixed;
}

this work for me !!!

quroom commented 3 years ago

I set scrollingContainer to html and add this css:

.ql-clipboard {
  position: fixed;
}

it fixed the problem for me. try it out.

This works me either.

jasonhoo95 commented 2 years ago

Hi why when i undo the scrollbar wont scroll to the position where the text should be undo, with auto height, instead if i set a height for ql-editor it will scroll to the position when undo occurs?

akshaykadambatt commented 1 year ago

image

After pasting the curser lands on the beginning of the pasted text, is there any fix for that?

janburchin commented 1 year ago

I set scrollingContainer to html and add this css:

.ql-clipboard {
  position: fixed;
}

it fixed the problem for me. try it out.

For me, it did not work until I added !important to the css, so now it looks like this:

.ql-clipboard {
      position: fixed !important;
}
indrajithvinodnair commented 1 year ago

This is the final fix which is inherited from the project's code itself.

Be sure to set the scrollingContainer to element that is responsible for scrolling the view. For example, let's say body element is responsible for scrolling the view, then we will have: scrollingContainer: html, body.

But sometimes body element is not responsible for scrolling the view, let's say we have an element around our Quill editor or anywhere inside of body element and it has a class of .editor-scroller, then we will have: scrollingContainer: .editor-scroller

import Quill, { RangeStatic, StringMap } from 'quill';
import Delta from 'quill-delta';

const ClipboardModule: any = Quill.import('modules/clipboard');

class CustomClipboard extends ClipboardModule {
  onPaste(event: ClipboardEvent): void {
    const range: RangeStatic = this.quill.getSelection();
    const formats: StringMap = this.quill.getFormat(range.index);
    const pastedDelta: Delta = this.convert(formats);
    const delta: Delta = new Delta()
      .retain(range.index)
      .delete(range.length)
      .concat(pastedDelta);
    this.quill.updateContents(delta, 'silent');
    // range.length contributes to delta.length()
    this.quill.setSelection(delta.length() - range.length, range.length, 'silent');
    this.quill.scrollIntoView();
  }
}

Quill.register('modules/clipboard', CustomClipboard, true);

The above code has been written in a TypeScript file, if you have a JavaScript file just remove type declarations.

For those who are going to write this in a TypeScript file, I used this library for Quill types: https://www.npmjs.com/package/@types/quill

This works. Thankyou

Edit: This works for the scroll jump issue, but introduces a new issue with respect to the styling of the pasted content. @ArsalanSavand

belowsurface3000 commented 1 year ago

For me all problems with scroll jumps were gone, after I had set scrollingContainer: document.documentElement

and where did you set this?

belowsurface3000 commented 1 year ago

I set scrollingContainer to html and add this css:

.ql-clipboard {
  position: fixed;
}

it fixed the problem for me. try it out.

For me, it did not work until I added !important to the css, so now it looks like this:

.ql-clipboard {
      position: fixed !important;
}

thanks a lot! this worked for me :)

SHENGALTEC commented 1 year ago

image

After pasting the curser lands on the beginning of the pasted text, is there any fix for that?

any fix for this issue yet?

nomad-vagabond commented 1 year ago

In my case setting 'position: fixed !important;' fixes the issue only in Firefox

.ql-clipboard {
      position: fixed !important;
}

But this setup by nabinbhusal80 works fine both in Firefox and Chromium:

.ql-clipboard {
    position: fixed !important;
    display: none;
    left: 50%;
    top: 50%;
  }
syedmoazam commented 1 year ago

@deepakjtrigent I found an issue when I implemented the below changes:

.ql-clipboard { height: 100%; width: 100%; position: absolute; left: 0; top: 0; z-index: -1; }

If some text has been selected in the editor and we try to paste some text to replace with the selected text. Some of the text is missing when I pasted the new text. Depending on the number of characters in the old text. Here is a video from https://quilljs.com/playground/

https://github.com/quilljs/quill/assets/79358912/247c6001-4b09-4760-ac66-c1810605a8c1

shahabkamali commented 1 year ago

this fixed my problem

var quill = new Quill('#editor', {
  modules: { toolbar: toolbarOptions },
    theme: 'snow',
    scrollingContainer: 'html'
});
Amoyens1s commented 8 months ago

this fixed my problem

var quill = new Quill('#editor', {
  modules: { toolbar: toolbarOptions },
    theme: 'snow',
    scrollingContainer: 'html'
});

wow! this works fine!!!! thanks!!

quill-bot commented 7 months ago

Quill 2.0 has been released (announcement post) with many changes and fixes. If this is still an issue please create a new issue after reviewing our updated Contributing guide :pray: