I’ve been moving blocks around in the WordPress Block Editor List View panel, and it sucks fucking hardcore. I don’t know who decided that the list needed to scroll based on how far the pointer was from the placement of the block, but dragging my mouse up and then down (or vice-versa), trying to nail the ‘smooth and intuitive’ (I’m sure) feature got to me so bad I finally developed a Must-Use plugin that fixes it.
Using this (drop the files as shown in the filenames into your WordPress installation’s mu-plugins directory) the List View panel in the (Gutenberg) Block Editor will behave like any other list view on your computer (hopefully) and only scroll near the top and bottom of the list.
Asher’s Human Bean Scrolling In List View
Here they are:
<?php
/**
* Plugin Name: Asher's Human Bean Scrolling In List View
* Description: Restricts block editor List View drag scrolling to the visible list edges.
* Author: Asher Wolfstein
* Version: 0.1.0
*/
declare ( strict_types = 1 ) ; defined( 'ABSPATH' ) || exit ;
require_once __DIR__
. '/asherwolfstein-outline-drag-scroll/asherwolfstein-outline-drag-scroll.php' ;
<?php declare ( strict_types = 1 ) ; defined( 'ABSPATH' ) || exit;
/** Load the List View drag-scroll guard in every block editor. */
function asherwolfstein_enqueue_outline_drag_scroll_guard(): void {
if ( ! is_readable( $asset_path
= __DIR__
. '/assets/asherwolfstein-outline-drag-scroll.js' )
) return ;
wp_enqueue_script( $handle
= 'asherwolfstein-outline-drag-scroll' ,
plugin_dir_url ( __FILE__ )
. 'assets/asherwolfstein-outline-drag-scroll.js' ,
[ 'wp-dom' , ] ,
( string ) filemtime ( $asset_path ) ,
true ) ;
/**
* Filters the top and bottom edge zone where List View drag scrolling is allowed.
*
* Use zero to disable automatic outline scrolling completely.
*
* @param int $edge_size Edge-zone size in CSS pixels.
*/
$edge_size
= max ( 0 ,
( int ) apply_filters ( 'asherwolfstein_outline_drag_scroll_edge_size', 24 ) ) ;
wp_add_inline_script( $handle ,
'window.AsherWolfsteinOutlineDragScroll = '
. wp_json_encode (
[ 'edgeSize' => $edge_size , ] ,
JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT )
. ';' ,
'before' ) ;
}
add_action ( 'enqueue_block_editor_assets' ,
'asherwolfstein_enqueue_outline_drag_scroll_guard' ) ;
And the supporting JavaScript:
( function (
window ,
document ,
wp
) {
'use strict' ;
const DEFAULT_EDGE_SIZE = 24 ;
const DRAGOVER_STALE_AFTER = 500 ;
const MAX_FRAME_DURATION = 50 ;
const MAX_SCROLL_SPEED = 480 ;
const MIN_EDGE_INTENT_DISTANCE = 4 ;
const config
= window.AsherWolfsteinOutlineDragScroll
|| {} ;
const configuredEdgeSize
= Number ( config.edgeSize ) ;
const edgeSize
= Number.isFinite ( configuredEdgeSize )
? Math.max ( 0 , configuredEdgeSize )
: DEFAULT_EDGE_SIZE ;
let activeDrag = null ;
function getIntendedEdgeStrength (
state
) {
if ( edgeSize
=== 0 ) return 0 ;
const listRect
= state.listView.getBoundingClientRect () ;
const containerRect
= state.scrollContainer.getBoundingClientRect () ;
const view
= state.view ;
const top
= Math.max ( listRect.top ,
containerRect.top ,
0 ) ;
const right
= Math.min ( listRect.right ,
containerRect.right ,
view.innerWidth ) ;
const bottom
= Math.min ( listRect.bottom ,
containerRect.bottom ,
view.innerHeight ) ;
const left
= Math.max ( listRect.left ,
containerRect.left ,
0 ) ;
if ( bottom
<= top
|| right
<= left
|| ! Number.isFinite ( state.pointerX )
|| ! Number.isFinite ( state.pointerY )
|| state.pointerX
< left
|| state.pointerX
> right
|| state.pointerY
< top
|| state.pointerY
> bottom
) return 0 ;
const activeEdgeSize
= Math.min ( edgeSize ,
( bottom
- top )
/ 2 ) ;
const strength
= state.pointerY
<= top
+ activeEdgeSize
? - ( ( top
+ activeEdgeSize
- state.pointerY )
/ activeEdgeSize )
: ( state.pointerY
>= bottom
- activeEdgeSize
? ( state.pointerY
- ( bottom
- activeEdgeSize ) )
/ activeEdgeSize
: 0 ) ;
return strength
< 0
&& state.pointerY
<= state.dragStartY
- MIN_EDGE_INTENT_DISTANCE
? strength
: ( strength
> 0
&& state.pointerY
>= state.dragStartY
+ MIN_EDGE_INTENT_DISTANCE
? strength
: 0 ) ;
}
function stopEdgeScrolling (
state
) {
if ( state.animationFrame
!== null
) { state.view.cancelAnimationFrame ( state.animationFrame ) ;
state.animationFrame
= null ; }
state.lastFrameTime
= null ;
state.edgeStrength
= 0 ;
}
function restoreScrollMethod () {
if ( ! activeDrag ) return ;
const state = activeDrag ;
activeDrag = null ;
stopEdgeScrolling ( state ) ;
if ( state.scrollContainer.scroll
!== state.guardedScroll
) return ;
if ( state.originalDescriptor ) {
Object.defineProperty ( state.scrollContainer ,
'scroll' ,
state.originalDescriptor ) ;
return ;
}
delete state.scrollContainer.scroll ;
}
function startListViewDrag ( event ) {
restoreScrollMethod () ;
const target = event.target ;
if ( ! ( target
instanceof window.Element )
) return ;
const listView
= target.closest ( '.block-editor-list-view-tree' ) ;
const draggableBlock
= target.closest ( '.block-editor-list-view-block-contents[draggable="true"]' ) ;
if ( ! listView
|| ! draggableBlock
) return ;
const scrollContainer
= ( wp
&& wp.dom
&& typeof wp.dom.getScrollContainer
=== 'function' )
? wp.dom.getScrollContainer ( target )
: null ;
if ( ! scrollContainer
|| typeof scrollContainer.scroll
!== 'function'
) return ;
const originalDescriptor
= Object.getOwnPropertyDescriptor ( scrollContainer,
'scroll' ) ;
const originalScroll
= scrollContainer.scroll ;
const ownerDocument
= scrollContainer.ownerDocument ;
const view
= ownerDocument.defaultView
|| window ;
const state
= { animationFrame : null ,
canScrollOutline : scrollContainer
!== ownerDocument.body
&&
scrollContainer
!== ownerDocument.documentElement ,
dragStartY : event.clientY ,
edgeStrength : 0 ,
guardedScroll : null ,
lastDragOverTime : null ,
lastFrameTime : null ,
listView ,
originalDescriptor ,
originalScroll ,
pointerX : event.clientX ,
pointerY : event.clientY ,
runAnimationFrame : null ,
scrollContainer ,
view , } ;
state.guardedScroll
= function (
...args
) {
const requestedTopValue
= ( args [ 0 ]
&& typeof args [ 0 ]
=== 'object' )
? args [ 0 ].top
: ( args.length
> 1
? args [ 1 ]
: undefined ) ;
const requestedTop
= Number ( requestedTopValue ) ;
if ( activeDrag
!== state
|| ! Number.isFinite ( requestedTop )
) return originalScroll.apply ( this , args ) ;
return undefined ;
} ;
try { Object.defineProperty ( scrollContainer ,
'scroll' ,
{ configurable : true,
value : state.guardedScroll,
writable : true, } ) ;
} catch ( error ) { return ; }
activeDrag = state ;
state.runAnimationFrame
= function (
timestamp
) {
state.animationFrame
= null ;
if ( activeDrag
!== state ) return ;
if ( state.lastDragOverTime
=== null
|| timestamp
- state.lastDragOverTime
> DRAGOVER_STALE_AFTER
) { stopEdgeScrolling ( state ) ; return ; }
const strength
= state.edgeStrength ;
if ( ! state.canScrollOutline
|| strength
=== 0
) { stopEdgeScrolling ( state ) ; return ; }
const elapsed
= state.lastFrameTime
=== null
? 0
: Math.min ( Math.max ( timestamp
- state.lastFrameTime ,
0 ) ,
MAX_FRAME_DURATION ) ;
state.lastFrameTime
= timestamp ;
if ( elapsed
> 0
) { const currentTop
= Number ( state.scrollContainer.scrollTop )
|| 0 ;
const maximumTop
= Math.max ( state.scrollContainer.scrollHeight
- state.scrollContainer.clientHeight ,
0 ) ;
const requestedTop
= Math.min ( Math.max ( currentTop
+ strength
* MAX_SCROLL_SPEED
* ( elapsed
/ 1000 ) ,
0 ) ,
maximumTop ) ;
if ( requestedTop
=== currentTop
) { stopEdgeScrolling ( state ) ; return ; }
state.originalScroll.call ( state.scrollContainer ,
{ top : requestedTop , } ) ;
}
state.animationFrame
= state.view.requestAnimationFrame ( state.runAnimationFrame ) ;
} ;
}
function updatePointerPosition ( event ) {
if ( ! activeDrag ) return ;
if ( ! Number.isFinite ( event.clientX )
|| ! Number.isFinite ( event.clientY )
) { activeDrag.lastDragOverTime = null ;
activeDrag.pointerX = null ;
activeDrag.pointerY = null ;
stopEdgeScrolling ( activeDrag ) ; return ; }
activeDrag.pointerX = event.clientX ;
activeDrag.pointerY = event.clientY ;
activeDrag.lastDragOverTime
= activeDrag.view.performance.now () ;
activeDrag.edgeStrength
= activeDrag.canScrollOutline
? getIntendedEdgeStrength ( activeDrag )
: 0 ;
if ( ! activeDrag.canScrollOutline
|| activeDrag.edgeStrength
=== 0
|| ( activeDrag.edgeStrength
< 0
&& activeDrag.scrollContainer.scrollTop
<= 0 )
|| ( activeDrag.edgeStrength
> 0
&& activeDrag.scrollContainer.scrollTop
>= activeDrag.scrollContainer.scrollHeight
- activeDrag.scrollContainer.clientHeight )
) { stopEdgeScrolling ( activeDrag ) ; return ; }
if ( activeDrag.animationFrame
=== null
) activeDrag.animationFrame
= activeDrag.view.requestAnimationFrame ( activeDrag.runAnimationFrame ) ;
}
function suspendEdgeScrolling ( event ) {
if ( ! activeDrag
|| event.relatedTarget
) return ;
activeDrag.lastDragOverTime = null ;
activeDrag.pointerX = null ;
activeDrag.pointerY = null ;
stopEdgeScrolling ( activeDrag ) ;
}
document.addEventListener ( 'dragstart' , startListViewDrag , true ) ;
document.addEventListener ( 'dragover' , updatePointerPosition , true ) ;
document.addEventListener ( 'dragleave' , suspendEdgeScrolling , true ) ;
document.addEventListener ( 'dragend' , restoreScrollMethod , true ) ;
document.addEventListener ( 'drop' , restoreScrollMethod , true ) ;
document.addEventListener ( 'keydown' ,
function ( event ) {
if ( event.key
=== 'Escape'
|| event.keyCode
=== 27
) restoreScrollMethod ()
} ,
true ) ;
document.addEventListener ( 'visibilitychange' ,
function () {
if ( document.hidden )
restoreScrollMethod () ;
} ) ;
window.addEventListener ( 'blur' , restoreScrollMethod ) ;
window.addEventListener ( 'pagehide' , restoreScrollMethod ) ;
} ) ( window ,
document ,
window.wp ) ;You can also just, you know, download this ZIP file. However, don’t use the Upload feature of the WordPress plugin panel, as it will most likely go to the wrong place! You’ll have to, as I wrote above, manually insert the asherwolfstein-outline-drag-scroll directory under wp-content/mu-plugins/ alongside the asherwolfstein-outline-drag-scroll.php loader file.
See ya ’round!
