Skip to content

Performance: only trigger dropzone handling when a user intends to drop an item#33435

Closed
gwwar wants to merge 6 commits intotrunkfrom
try/dropzone-drag-intent
Closed

Performance: only trigger dropzone handling when a user intends to drop an item#33435
gwwar wants to merge 6 commits intotrunkfrom
try/dropzone-drag-intent

Conversation

@gwwar
Copy link
Copy Markdown
Contributor

@gwwar gwwar commented Jul 14, 2021

Background

Our drop zone handlers are also causing major performance issues when dragging quickly. We have dragging listeners on every component that is a drop zone. These events are currently not throttled and when dragging quickly we can queue up quite a number of events to resolve. We will also trigger a number of unneeded animations. This slows things down quite a bit.

In this PR we try and infer what the user intends to do. Are they trying to move the item quickly, someplace else or are they ready to drop an item into a drop zone? If they're not ready to drop, we can skip work.

I profiled using the Armando theme, and the "alternating rows with images and headings" pattern.

before after
before after-16

Here are some videos of the type of actions I was performing while profiling. Note that screenrecording + profiling also impacts performance, so I took separate profiling screencaps above.

Profiling In Trunk

before.mp4

Branch

after-16.mp4

Testing Instructions

  • No regressions in dropzone
  • Verify that the drop indicators feel responsive enough. (We can still tune this further if we're sampling not often enough.)

Follow Up

To improve drag performance further I think there's two areas of interest I've spotted:

  • Incremental follow up of exploring if dragleave events can be throttled further.
  • Invert control, and explore a global drag handler instead of many child listeners trying to cancel each other's events. Much larger refactor, but I'd typically pick this setup for a complex editor/app.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Jul 14, 2021

Size Change: +111 B (0%)

Total Size: 1.08 MB

Filename Size Change
build/compose/index.min.js 10.3 kB +111 B (+1%)
ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 1.12 kB
build/admin-manifest/index.min.js 1.46 kB
build/annotations/index.min.js 2.93 kB
build/api-fetch/index.min.js 2.44 kB
build/autop/index.min.js 2.28 kB
build/blob/index.min.js 673 B
build/block-directory/index.min.js 6.62 kB
build/block-directory/style-rtl.css 1.02 kB
build/block-directory/style.css 1.02 kB
build/block-editor/index.min.js 127 kB
build/block-editor/style-rtl.css 14 kB
build/block-editor/style.css 14 kB
build/block-library/blocks/archives/editor-rtl.css 61 B
build/block-library/blocks/archives/editor.css 60 B
build/block-library/blocks/archives/style-rtl.css 65 B
build/block-library/blocks/archives/style.css 65 B
build/block-library/blocks/audio/editor-rtl.css 58 B
build/block-library/blocks/audio/editor.css 58 B
build/block-library/blocks/audio/style-rtl.css 112 B
build/block-library/blocks/audio/style.css 112 B
build/block-library/blocks/audio/theme-rtl.css 125 B
build/block-library/blocks/audio/theme.css 125 B
build/block-library/blocks/block/editor-rtl.css 161 B
build/block-library/blocks/block/editor.css 161 B
build/block-library/blocks/button/editor-rtl.css 475 B
build/block-library/blocks/button/editor.css 474 B
build/block-library/blocks/button/style-rtl.css 603 B
build/block-library/blocks/button/style.css 602 B
build/block-library/blocks/buttons/editor-rtl.css 315 B
build/block-library/blocks/buttons/editor.css 315 B
build/block-library/blocks/buttons/style-rtl.css 375 B
build/block-library/blocks/buttons/style.css 375 B
build/block-library/blocks/calendar/style-rtl.css 208 B
build/block-library/blocks/calendar/style.css 208 B
build/block-library/blocks/categories/editor-rtl.css 84 B
build/block-library/blocks/categories/editor.css 83 B
build/block-library/blocks/categories/style-rtl.css 79 B
build/block-library/blocks/categories/style.css 79 B
build/block-library/blocks/code/style-rtl.css 90 B
build/block-library/blocks/code/style.css 90 B
build/block-library/blocks/code/theme-rtl.css 131 B
build/block-library/blocks/code/theme.css 131 B
build/block-library/blocks/columns/editor-rtl.css 190 B
build/block-library/blocks/columns/editor.css 190 B
build/block-library/blocks/columns/style-rtl.css 475 B
build/block-library/blocks/columns/style.css 476 B
build/block-library/blocks/cover/editor-rtl.css 670 B
build/block-library/blocks/cover/editor.css 670 B
build/block-library/blocks/cover/style-rtl.css 1.22 kB
build/block-library/blocks/cover/style.css 1.23 kB
build/block-library/blocks/embed/editor-rtl.css 486 B
build/block-library/blocks/embed/editor.css 486 B
build/block-library/blocks/embed/style-rtl.css 401 B
build/block-library/blocks/embed/style.css 400 B
build/block-library/blocks/embed/theme-rtl.css 124 B
build/block-library/blocks/embed/theme.css 124 B
build/block-library/blocks/file/editor-rtl.css 301 B
build/block-library/blocks/file/editor.css 300 B
build/block-library/blocks/file/style-rtl.css 255 B
build/block-library/blocks/file/style.css 255 B
build/block-library/blocks/file/view.min.js 711 B
build/block-library/blocks/freeform/editor-rtl.css 2.44 kB
build/block-library/blocks/freeform/editor.css 2.44 kB
build/block-library/blocks/gallery/editor-rtl.css 704 B
build/block-library/blocks/gallery/editor.css 705 B
build/block-library/blocks/gallery/style-rtl.css 1.06 kB
build/block-library/blocks/gallery/style.css 1.06 kB
build/block-library/blocks/gallery/theme-rtl.css 122 B
build/block-library/blocks/gallery/theme.css 122 B
build/block-library/blocks/group/editor-rtl.css 160 B
build/block-library/blocks/group/editor.css 160 B
build/block-library/blocks/group/style-rtl.css 57 B
build/block-library/blocks/group/style.css 57 B
build/block-library/blocks/group/theme-rtl.css 93 B
build/block-library/blocks/group/theme.css 93 B
build/block-library/blocks/heading/editor-rtl.css 152 B
build/block-library/blocks/heading/editor.css 152 B
build/block-library/blocks/heading/style-rtl.css 76 B
build/block-library/blocks/heading/style.css 76 B
build/block-library/blocks/home-link/style-rtl.css 247 B
build/block-library/blocks/home-link/style.css 247 B
build/block-library/blocks/html/editor-rtl.css 281 B
build/block-library/blocks/html/editor.css 281 B
build/block-library/blocks/image/editor-rtl.css 729 B
build/block-library/blocks/image/editor.css 727 B
build/block-library/blocks/image/style-rtl.css 481 B
build/block-library/blocks/image/style.css 485 B
build/block-library/blocks/image/theme-rtl.css 124 B
build/block-library/blocks/image/theme.css 124 B
build/block-library/blocks/latest-comments/style-rtl.css 286 B
build/block-library/blocks/latest-comments/style.css 286 B
build/block-library/blocks/latest-posts/editor-rtl.css 137 B
build/block-library/blocks/latest-posts/editor.css 137 B
build/block-library/blocks/latest-posts/style-rtl.css 526 B
build/block-library/blocks/latest-posts/style.css 524 B
build/block-library/blocks/list/style-rtl.css 63 B
build/block-library/blocks/list/style.css 63 B
build/block-library/blocks/media-text/editor-rtl.css 263 B
build/block-library/blocks/media-text/editor.css 264 B
build/block-library/blocks/media-text/style-rtl.css 492 B
build/block-library/blocks/media-text/style.css 489 B
build/block-library/blocks/more/editor-rtl.css 434 B
build/block-library/blocks/more/editor.css 434 B
build/block-library/blocks/navigation-link/editor-rtl.css 474 B
build/block-library/blocks/navigation-link/editor.css 473 B
build/block-library/blocks/navigation-link/style-rtl.css 94 B
build/block-library/blocks/navigation-link/style.css 94 B
build/block-library/blocks/navigation/editor-rtl.css 1.69 kB
build/block-library/blocks/navigation/editor.css 1.69 kB
build/block-library/blocks/navigation/style-rtl.css 1.65 kB
build/block-library/blocks/navigation/style.css 1.66 kB
build/block-library/blocks/navigation/view.min.js 2.84 kB
build/block-library/blocks/nextpage/editor-rtl.css 395 B
build/block-library/blocks/nextpage/editor.css 395 B
build/block-library/blocks/page-list/editor-rtl.css 310 B
build/block-library/blocks/page-list/editor.css 311 B
build/block-library/blocks/page-list/style-rtl.css 240 B
build/block-library/blocks/page-list/style.css 240 B
build/block-library/blocks/paragraph/editor-rtl.css 157 B
build/block-library/blocks/paragraph/editor.css 157 B
build/block-library/blocks/paragraph/style-rtl.css 247 B
build/block-library/blocks/paragraph/style.css 248 B
build/block-library/blocks/post-author/editor-rtl.css 209 B
build/block-library/blocks/post-author/editor.css 209 B
build/block-library/blocks/post-author/style-rtl.css 183 B
build/block-library/blocks/post-author/style.css 184 B
build/block-library/blocks/post-comments-form/style-rtl.css 140 B
build/block-library/blocks/post-comments-form/style.css 140 B
build/block-library/blocks/post-comments/style-rtl.css 360 B
build/block-library/blocks/post-comments/style.css 359 B
build/block-library/blocks/post-content/editor-rtl.css 139 B
build/block-library/blocks/post-content/editor.css 139 B
build/block-library/blocks/post-excerpt/editor-rtl.css 73 B
build/block-library/blocks/post-excerpt/editor.css 73 B
build/block-library/blocks/post-excerpt/style-rtl.css 69 B
build/block-library/blocks/post-excerpt/style.css 69 B
build/block-library/blocks/post-featured-image/editor-rtl.css 338 B
build/block-library/blocks/post-featured-image/editor.css 338 B
build/block-library/blocks/post-featured-image/style-rtl.css 141 B
build/block-library/blocks/post-featured-image/style.css 141 B
build/block-library/blocks/post-template/editor-rtl.css 100 B
build/block-library/blocks/post-template/editor.css 99 B
build/block-library/blocks/post-template/style-rtl.css 379 B
build/block-library/blocks/post-template/style.css 380 B
build/block-library/blocks/post-terms/style-rtl.css 73 B
build/block-library/blocks/post-terms/style.css 73 B
build/block-library/blocks/post-title/style-rtl.css 60 B
build/block-library/blocks/post-title/style.css 60 B
build/block-library/blocks/preformatted/style-rtl.css 103 B
build/block-library/blocks/preformatted/style.css 103 B
build/block-library/blocks/pullquote/editor-rtl.css 198 B
build/block-library/blocks/pullquote/editor.css 198 B
build/block-library/blocks/pullquote/style-rtl.css 362 B
build/block-library/blocks/pullquote/style.css 361 B
build/block-library/blocks/pullquote/theme-rtl.css 169 B
build/block-library/blocks/pullquote/theme.css 169 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 122 B
build/block-library/blocks/query-pagination-numbers/editor.css 121 B
build/block-library/blocks/query-pagination/editor-rtl.css 270 B
build/block-library/blocks/query-pagination/editor.css 262 B
build/block-library/blocks/query-pagination/style-rtl.css 168 B
build/block-library/blocks/query-pagination/style.css 168 B
build/block-library/blocks/query-title/editor-rtl.css 86 B
build/block-library/blocks/query-title/editor.css 86 B
build/block-library/blocks/query/editor-rtl.css 131 B
build/block-library/blocks/query/editor.css 132 B
build/block-library/blocks/quote/style-rtl.css 169 B
build/block-library/blocks/quote/style.css 169 B
build/block-library/blocks/quote/theme-rtl.css 221 B
build/block-library/blocks/quote/theme.css 221 B
build/block-library/blocks/rss/editor-rtl.css 201 B
build/block-library/blocks/rss/editor.css 202 B
build/block-library/blocks/rss/style-rtl.css 290 B
build/block-library/blocks/rss/style.css 290 B
build/block-library/blocks/search/editor-rtl.css 211 B
build/block-library/blocks/search/editor.css 211 B
build/block-library/blocks/search/style-rtl.css 372 B
build/block-library/blocks/search/style.css 373 B
build/block-library/blocks/search/theme-rtl.css 64 B
build/block-library/blocks/search/theme.css 64 B
build/block-library/blocks/separator/editor-rtl.css 99 B
build/block-library/blocks/separator/editor.css 99 B
build/block-library/blocks/separator/style-rtl.css 251 B
build/block-library/blocks/separator/style.css 251 B
build/block-library/blocks/separator/theme-rtl.css 172 B
build/block-library/blocks/separator/theme.css 172 B
build/block-library/blocks/shortcode/editor-rtl.css 476 B
build/block-library/blocks/shortcode/editor.css 476 B
build/block-library/blocks/site-logo/editor-rtl.css 465 B
build/block-library/blocks/site-logo/editor.css 465 B
build/block-library/blocks/site-logo/style-rtl.css 154 B
build/block-library/blocks/site-logo/style.css 154 B
build/block-library/blocks/site-tagline/editor-rtl.css 87 B
build/block-library/blocks/site-tagline/editor.css 87 B
build/block-library/blocks/site-title/editor-rtl.css 85 B
build/block-library/blocks/site-title/editor.css 85 B
build/block-library/blocks/social-link/editor-rtl.css 164 B
build/block-library/blocks/social-link/editor.css 165 B
build/block-library/blocks/social-links/editor-rtl.css 800 B
build/block-library/blocks/social-links/editor.css 799 B
build/block-library/blocks/social-links/style-rtl.css 1.34 kB
build/block-library/blocks/social-links/style.css 1.34 kB
build/block-library/blocks/spacer/editor-rtl.css 308 B
build/block-library/blocks/spacer/editor.css 308 B
build/block-library/blocks/spacer/style-rtl.css 48 B
build/block-library/blocks/spacer/style.css 48 B
build/block-library/blocks/table/editor-rtl.css 478 B
build/block-library/blocks/table/editor.css 478 B
build/block-library/blocks/table/style-rtl.css 480 B
build/block-library/blocks/table/style.css 480 B
build/block-library/blocks/table/theme-rtl.css 188 B
build/block-library/blocks/table/theme.css 188 B
build/block-library/blocks/tag-cloud/style-rtl.css 146 B
build/block-library/blocks/tag-cloud/style.css 146 B
build/block-library/blocks/template-part/editor-rtl.css 551 B
build/block-library/blocks/template-part/editor.css 550 B
build/block-library/blocks/template-part/theme-rtl.css 101 B
build/block-library/blocks/template-part/theme.css 101 B
build/block-library/blocks/term-description/editor-rtl.css 90 B
build/block-library/blocks/term-description/editor.css 90 B
build/block-library/blocks/text-columns/editor-rtl.css 95 B
build/block-library/blocks/text-columns/editor.css 95 B
build/block-library/blocks/text-columns/style-rtl.css 166 B
build/block-library/blocks/text-columns/style.css 166 B
build/block-library/blocks/verse/style-rtl.css 87 B
build/block-library/blocks/verse/style.css 87 B
build/block-library/blocks/video/editor-rtl.css 569 B
build/block-library/blocks/video/editor.css 570 B
build/block-library/blocks/video/style-rtl.css 173 B
build/block-library/blocks/video/style.css 173 B
build/block-library/blocks/video/theme-rtl.css 124 B
build/block-library/blocks/video/theme.css 124 B
build/block-library/common-rtl.css 1.29 kB
build/block-library/common.css 1.29 kB
build/block-library/editor-rtl.css 9.83 kB
build/block-library/editor.css 9.83 kB
build/block-library/index.min.js 146 kB
build/block-library/reset-rtl.css 514 B
build/block-library/reset.css 515 B
build/block-library/style-rtl.css 10.3 kB
build/block-library/style.css 10.3 kB
build/block-library/theme-rtl.css 692 B
build/block-library/theme.css 693 B
build/block-serialization-default-parser/index.min.js 1.3 kB
build/block-serialization-spec-parser/index.min.js 3.06 kB
build/blocks/index.min.js 47.2 kB
build/components/index.min.js 197 kB
build/components/style-rtl.css 15.9 kB
build/components/style.css 15.9 kB
build/core-data/index.min.js 12.4 kB
build/customize-widgets/index.min.js 10.3 kB
build/customize-widgets/style-rtl.css 1.48 kB
build/customize-widgets/style.css 1.48 kB
build/data-controls/index.min.js 829 B
build/data/index.min.js 7.22 kB
build/date/index.min.js 31.8 kB
build/deprecated/index.min.js 738 B
build/dom-ready/index.min.js 576 B
build/dom/index.min.js 4.78 kB
build/edit-navigation/index.min.js 13.9 kB
build/edit-navigation/style-rtl.css 3.12 kB
build/edit-navigation/style.css 3.12 kB
build/edit-post/classic-rtl.css 483 B
build/edit-post/classic.css 483 B
build/edit-post/index.min.js 59.4 kB
build/edit-post/style-rtl.css 7.25 kB
build/edit-post/style.css 7.24 kB
build/edit-site/index.min.js 26 kB
build/edit-site/style-rtl.css 5.04 kB
build/edit-site/style.css 5.03 kB
build/edit-widgets/index.min.js 16.2 kB
build/edit-widgets/style-rtl.css 3.99 kB
build/edit-widgets/style.css 3.99 kB
build/editor/index.min.js 38.7 kB
build/editor/style-rtl.css 3.96 kB
build/editor/style.css 3.96 kB
build/element/index.min.js 3.44 kB
build/escape-html/index.min.js 739 B
build/format-library/index.min.js 5.72 kB
build/format-library/style-rtl.css 668 B
build/format-library/style.css 669 B
build/hooks/index.min.js 1.76 kB
build/html-entities/index.min.js 628 B
build/i18n/index.min.js 3.73 kB
build/is-shallow-equal/index.min.js 710 B
build/keyboard-shortcuts/index.min.js 1.74 kB
build/keycodes/index.min.js 1.49 kB
build/list-reusable-blocks/index.min.js 2.07 kB
build/list-reusable-blocks/style-rtl.css 842 B
build/list-reusable-blocks/style.css 842 B
build/media-utils/index.min.js 3.08 kB
build/notices/index.min.js 1.07 kB
build/nux/index.min.js 2.31 kB
build/nux/style-rtl.css 745 B
build/nux/style.css 742 B
build/plugins/index.min.js 1.99 kB
build/primitives/index.min.js 1.06 kB
build/priority-queue/index.min.js 791 B
build/react-i18n/index.min.js 924 B
build/redux-routine/index.min.js 2.82 kB
build/reusable-blocks/index.min.js 2.56 kB
build/reusable-blocks/style-rtl.css 256 B
build/reusable-blocks/style.css 256 B
build/rich-text/index.min.js 10.8 kB
build/server-side-render/index.min.js 1.64 kB
build/shortcode/index.min.js 1.68 kB
build/token-list/index.min.js 848 B
build/url/index.min.js 1.95 kB
build/viewport/index.min.js 1.28 kB
build/warning/index.min.js 1.16 kB
build/widgets/index.min.js 6.48 kB
build/widgets/style-rtl.css 1.04 kB
build/widgets/style.css 1.05 kB
build/wordcount/index.min.js 1.24 kB

compressed-size-action

@gwwar
Copy link
Copy Markdown
Contributor Author

gwwar commented Jul 16, 2021

I did a little side exploration (with extremely rough code), to see what would happen if we added one set of global drag handlers to the document, instead of per drop zone in 9eee304

I'm going to defer on this approach for now, since the chance of regression is a bit too high, but I think it might be worth revisiting if we're still seeing some bad performance characteristics with drag in the future.

A few things I discovered:

global_experiment.mp4

@gwwar
Copy link
Copy Markdown
Contributor Author

gwwar commented Jul 16, 2021

Ran out of time, but I did a very basic distance check in 4adbcf7 which looks promising.

Things to investigate next week would be:

  • See if we can throttle dragover and cancel the dragenter event instead, to make sure the drop event is available.
  • Make sure we're calculating relative distance, taking into account the time passed from the last frame. We'll need this to better tune when insertion hints show up.
basic.distance.check.mp4

@gwwar gwwar force-pushed the try/dropzone-drag-intent branch from 4adbcf7 to a445056 Compare July 23, 2021 16:11
@gwwar gwwar self-assigned this Jul 23, 2021
@gwwar gwwar marked this pull request as ready for review July 23, 2021 17:27
@gwwar gwwar requested a review from ajitbohra as a code owner July 23, 2021 17:27
@gwwar gwwar added [Type] Performance Related to performance efforts [Package] Compose /packages/compose labels Jul 23, 2021
@gwwar
Copy link
Copy Markdown
Contributor Author

gwwar commented Jul 23, 2021

This one is ready for review 👍

@gwwar
Copy link
Copy Markdown
Contributor Author

gwwar commented Jul 23, 2021

List view allows a user to drag a block to a new sibling position

Hmm, looks like that's unhappy in CI, but works locally for me. I'll try and see if other tuning options work out a bit better.

@gwwar
Copy link
Copy Markdown
Contributor Author

gwwar commented Jul 23, 2021

Updated the summary with the new profiling timings with the 33ms sampling.

@gwwar gwwar requested review from nerrad and ntwb as code owners July 23, 2021 20:12
lastDragX = event.clientX;
lastDragY = event.clientY;
lastTime = time;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would be the difference with lodash's throttle? If we keep this type of throttling, any change we can abstract this out of this hook?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would be the difference with lodash's throttle?

Great question! So using some of the nice visualizations from: https://css-tricks.com/debouncing-throttling-explained-examples/

Debounce

debounce

Debounce turns a number of raw events into a single (usually trailing) event call for some interval. This is really great for things like search inputs/API calls. Say one API call to search for "cats" instead of four API requests for "c", "ca", "cat", "cats" (which might resolve in incorrect order).

Throttle

throttle

With throttle we don’t allow to our function to execute more than once every X milliseconds. This usually works well with things involving animation, since we'd like to see all the inbetween work at a consistent rate. For example, imagine if a scroll handler skipped all the way to the top or bottom using debounce without animating the inbetween frames, it'd be jarring.

Heuristic - Is the user dragging slowly?

In this case, I'm using a pretty basic heuristic of is the user dragging slowly? If they are, we can guess that they intend to drop an item soon and so we should show drag inserters/hints (with all the fancy expensive animations that come with that). If not we can skip work.

What's tricky is tuning the sensitivity. If it's too sensitive we'll show the drag inserters too often (and slow down dragging), if it's not sensitive enough users will have trouble dropping items into dropzones.

A great way of visualizing this is with an old jQuery plugin hover intent that uses mouse velocity to trigger if a hover event should fire or not https://briancherne.github.io/jquery-hoverIntent/:

See how moving the mouse back and forth in the first row can queue up a lot of events, while the second row is closer to what a user would expect for interactive behavior?

hoverIntent.mp4

If we keep this type of throttling, any change we can abstract this out of this hook?

We can certainly pull out a mouse velocity/user intent piece utility with sensitivity/timing options, but I'd maybe hold off until we have a second example. It looks like it's somewhat rare for us to need to tune raw event handling like this.

If you know that we already have a user intent/mouse velocity library in place already, I'm happy to update the implementation too.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not worried about reusability, I'm more worried about polluting useDropZone with timing specific things. I just have the feeling that we're making the hook less pure, which is only meant to act as a thin wrapper around the native drag and drop API.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I'll see if I we can tidy things a bit more without affecting cost too much

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ellatrix were you thinking of something like 2bfbe23 or b280bb0 ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great comment https://github.com/WordPress/gutenberg/pull/33435/files#r676775855 that I think deserves to be an inline documentation or an architecture doc.

Is this behavior similar to the drag and scroll behavior we have where we also compute mouse velocity? Could this be considered a second use-case for the same utility.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think deserves to be an inline documentation or an architecture doc.

Sure, happy to add one. Maybe a subsection for common strategies in https://github.com/WordPress/gutenberg/blob/trunk/docs/explanations/architecture/performance.md ?

Is this behavior similar to the drag and scroll behavior we have where we also compute mouse velocity? Could this be considered a second use-case for the same utility.

If we're computing mouse velocity elsewhere, we can potentially reuse it for this handler. @youknowriad do you have a link to the file for where this happens? I can try incorporating it here or in a follow up.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, happy to add one. Maybe a subsection for common strategies in https://github.com/WordPress/gutenberg/blob/trunk/docs/explanations/architecture/performance.md ?

Sounds good to me.

If we're computing mouse velocity elsewhere, we can potentially reuse it for this handler. @youknowriad do you have a link to the file for where this happens? I can try incorporating it here or in a follow up.

I'm not very familiar with this hook but I think this is where we're kind of doing similar things:

https://github.com/WordPress/gutenberg/blob/trunk/packages/block-editor/src/components/block-draggable/use-scroll-when-dragging.js

The idea of that hook is to scroll the container as we get to the edge with the mouse but there's something about velocity as well, if you're moving fast, it scrolls faster.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think this is worth separating for clarity, ideally just a wrapper function similar to throttle and debounce that the implementor of the drop zone can add around the callbacks. useDropZone is otherwise a simple utility around the native drag and drop API to make it easier to work with, it shouldn't have any opinions about the rest imho.

@talldan talldan mentioned this pull request Jul 26, 2021
37 tasks
//first frame
lastTime = time;
lastDragX = event.clientX;
lastDragY = event.clientY;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these be set on drag start?


return await page.mouse.dragAndDrop( draggablePoint, targetPoint );
return await page.mouse.dragAndDrop( draggablePoint, targetPoint, {
delay: 100,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this 100 coming from?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This E2E was passing locally but not in CI, the 100ms is pretty arbitrary, but we could try other smaller values.

@gwwar gwwar force-pushed the try/dropzone-drag-intent branch from e22b2f3 to 2bfbe23 Compare July 26, 2021 21:16
@gwwar
Copy link
Copy Markdown
Contributor Author

gwwar commented Jul 27, 2021

Going to test a few other sensitivity values

@gwwar gwwar force-pushed the try/dropzone-drag-intent branch from b280bb0 to 914e188 Compare July 27, 2021 16:28
@gwwar
Copy link
Copy Markdown
Contributor Author

gwwar commented Jul 27, 2021

@talldan much appreciated if you could see if drag performance feels as responsive (or better) in the persistent list view.

Copy link
Copy Markdown
Contributor

@talldan talldan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are they trying to move the item quickly, someplace else or are they ready to drop an item into a drop zone? If they're not ready to drop, we can skip work.

I think this aspect makes it feel less responsive. For example, if I'm dragging in the demo post in List View, and move my cursor quite quickly, here's what I observe:

  • In trunk, the blue drop indicator follows my cursor much more closely, which makes it feel responsive (even if it is more computationally expensive)
  • In this branch, the blue drop indicator stays in place momentarily and only updates a few times before my cursor reaches its destination.

This is one of those interesting cases where UX intersects performance. Is the right behavior for the drop indicator to update so much? Probably not. There might be some interesting experimentation here - like when the user is dragging quickly the indicator could just follow the cursor (a simple calculation), and only when the cursor settles in a position could it snap to the right block position (the complex calculation). I don't know exactly how that would feel but it'd be interesting to try.

Thinking as well about how things work in video games, they usually have a fixed rendering loop and a variable 'update' loop working separately, so you get fixed animations frame rates with the 60fps goal, but the business logic (physics and other game logic) could be working slower or faster than that. A new loop is only triggered when the previous one has completed. I think that could manifest itself as more of a locking mechanism, where onDragOverRef.current is only called if previous work has completed.

Sorry, this turned into quite a long review without many conclusions!

Comment on lines +159 to +165
const distance =
Math.abs(
event.clientY - dragTimingRef.current.dragY
) +
Math.abs(
event.clientX - dragTimingRef.current.dragX
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think distance should be something like Math.sqrt( Math.pow( x1 - x2, 2 ) + Math.pow( y1 - y2, 2 ) ).

(https://paulrohan.medium.com/euclidean-distance-and-normalization-of-a-vector-76f7a97abd9)

}
dragTimingRef.current.dragX = event.clientX;
dragTimingRef.current.dragY = event.clientY;
dragTimingRef.current.timestamp = time;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if the timestamp should perhaps only be updated if the onDragOverRef.current callback has been called. As it stands, this will throttle future calls even if nothing happened the last time around because the user didn't drag far enough to trigger the callback.

The alternative, having it within the if statement, means that <16ms could have passed since the last onDragOver event, but it's really >16ms since the last time we did any actual work, so the callback can be triggered.

Math.abs(
event.clientX - dragTimingRef.current.dragX
);
if ( distance < 10 ) {
Copy link
Copy Markdown
Contributor

@talldan talldan Jul 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the way this stands this is really a speed calculation. The cursor has to travel at slower than 10pixels per ~16ms to trigger an update.

Nothing to do here, just noting my lightbulb moment 😄

@gwwar
Copy link
Copy Markdown
Contributor Author

gwwar commented Jul 28, 2021

There might be some interesting experimentation here - like when the user is dragging quickly the indicator could just follow the cursor (a simple calculation), and only when the cursor settles in a position could it snap to the right block position (the complex calculation). I don't know exactly how that would feel but it'd be interesting to try.

🤔 @talldan One thing we can try first is provide better drag hints (vs the 1px line) in a separate PR. This would give us better UX and would make this a bit more obvious as we try and tune drag performance a bit.

This is one of those interesting cases where UX intersects performance.

💯 For example, I'm quite aware of how slow/sluggish that drag chip is on trunk, to the point where I normally don't try to perform any editing actions with drag. At the same time it'll be pretty frustrating for folks if dropping items into an area isn't responsive enough (eg we had a false negative that they were trying to drag an item somewhere else quickly.)

A new loop is only triggered when the previous one has completed. I think that could manifest itself as more of a locking mechanism

Work/render loops can be quite useful in complicated apps. It's possible for us to also use requestAnimationFrame here if we like results. (As an aside a single global drag handler that calls the right child callbacks is much faster, but also is a big reactor).

@gwwar
Copy link
Copy Markdown
Contributor Author

gwwar commented Aug 11, 2021

Going to close this one out for now in favor of some new drag interaction experiments

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Package] Compose /packages/compose [Type] Performance Related to performance efforts

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants