8
0
Эх сурвалжийг харах

Merge pull request #108 from ckeditor/t/ckeditor5-image/310

Other: Improved the resizer performance. Closes ckeditor/ckeditor5#5191.
Piotrek Koszuliński 6 жил өмнө
parent
commit
e13c309b22

+ 82 - 23
packages/ckeditor5-widget/src/widgetresize.js

@@ -11,6 +11,8 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import Resizer from './widgetresize/resizer';
 import DomEmitterMixin from '@ckeditor/ckeditor5-utils/src/dom/emittermixin';
 import global from '@ckeditor/ckeditor5-utils/src/dom/global';
+import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
+import mix from '@ckeditor/ckeditor5-utils/src/mix';
 import { throttle } from 'lodash-es';
 
 import '../theme/widgetresize.css';
@@ -21,6 +23,7 @@ import '../theme/widgetresize.css';
  * Use the {@link module:widget/widgetresize~WidgetResize#attachTo} method to create a resizer for the specified widget.
  *
  * @extends module:core/plugin~Plugin
+ * @mixes module:utils/observablemixin~ObservableMixin
  */
 export default class WidgetResize extends Plugin {
 	/**
@@ -31,11 +34,35 @@ export default class WidgetResize extends Plugin {
 	}
 
 	init() {
-		this.resizers = [];
-		this.activeResizer = null;
+		/**
+		 * The currently visible resizer.
+		 *
+		 * @protected
+		 * @observable
+		 * @member {module:widget/widgetresize/resizer~Resizer|null} #_visibleResizer
+		 */
+		this.set( '_visibleResizer', null );
+
+		/**
+		 * References an active resizer.
+		 *
+		 * Active resizer means a resizer which handle is actively used by the end user.
+		 *
+		 * @protected
+		 * @observable
+		 * @member {module:widget/widgetresize/resizer~Resizer|null} #_activeResizer
+		 */
+		this.set( '_activeResizer', null );
+
+		/**
+		 * A map of resizers created using this plugin instance.
+		 *
+		 * @private
+		 * @type {Map.<module:engine/view/containerelement~ContainerElement, module:widget/widgetresize/resizer~Resizer>}
+		 */
+		this._resizers = new Map();
 
 		const domDocument = global.window.document;
-		const THROTTLE_THRESHOLD = 16; // 16ms = ~60fps
 
 		this.editor.model.schema.setAttributeProperties( 'width', {
 			isFormatting: true
@@ -50,38 +77,52 @@ export default class WidgetResize extends Plugin {
 
 			const resizeHandle = domEventData.target;
 
-			this.activeResizer = this._getResizerByHandle( resizeHandle );
+			this._activeResizer = this._getResizerByHandle( resizeHandle );
 
-			if ( this.activeResizer ) {
-				this.activeResizer.begin( resizeHandle );
+			if ( this._activeResizer ) {
+				this._activeResizer.begin( resizeHandle );
 			}
 		} );
 
-		this._observer.listenTo( domDocument, 'mousemove', throttle( ( event, domEventData ) => {
-			if ( this.activeResizer ) {
-				this.activeResizer.updateSize( domEventData );
+		this._observer.listenTo( domDocument, 'mousemove', ( event, domEventData ) => {
+			if ( this._activeResizer ) {
+				this._activeResizer.updateSize( domEventData );
 			}
-		}, THROTTLE_THRESHOLD ) );
+		} );
 
 		this._observer.listenTo( domDocument, 'mouseup', () => {
-			if ( this.activeResizer ) {
-				this.activeResizer.commit();
+			if ( this._activeResizer ) {
+				this._activeResizer.commit();
 
-				this.activeResizer = null;
+				this._activeResizer = null;
 			}
 		} );
 
-		const redrawResizers = throttle( () => {
-			for ( const context of this.resizers ) {
-				context.redraw();
+		const redrawFocusedResizer = () => {
+			if ( this._visibleResizer ) {
+				this._visibleResizer.redraw();
 			}
-		}, THROTTLE_THRESHOLD );
+		};
+
+		const redrawFocusedResizerThrottled = throttle( redrawFocusedResizer, 200 ); // 5fps
+
+		// Redraws occurring upon a change of visible resizer must not be throttled, as it is crucial for the initial
+		// render. Without it the resizer frame would be misaligned with resizing host for a fraction of second.
+		this.on( 'change:_visibleResizer', redrawFocusedResizer );
 
 		// Redrawing on any change of the UI of the editor (including content changes).
-		this.editor.ui.on( 'update', redrawResizers );
+		this.editor.ui.on( 'update', redrawFocusedResizerThrottled );
 
 		// Resizers need to be redrawn upon window resize, because new window might shrink resize host.
-		this._observer.listenTo( global.window, 'resize', redrawResizers );
+		this._observer.listenTo( global.window, 'resize', redrawFocusedResizerThrottled );
+
+		const viewSelection = this.editor.editing.view.document.selection;
+
+		viewSelection.on( 'change', () => {
+			const selectedElement = viewSelection.getSelectedElement();
+
+			this._visibleResizer = this._getResizerByViewElement( selectedElement ) || null;
+		} );
 	}
 
 	destroy() {
@@ -97,22 +138,40 @@ export default class WidgetResize extends Plugin {
 
 		resizer.attach();
 
-		this.editor.editing.view.once( 'render', () => resizer.redraw() );
-
-		this.resizers.push( resizer );
+		this._resizers.set( options.viewElement, resizer );
 
 		return resizer;
 	}
 
+	/**
+	 * Returns a resizer that contains a given resize handle.
+	 *
+	 * @protected
+	 * @param {HTMLElement} domResizeHandle
+	 * @returns {module:widget/widgetresize/resizer~Resizer}
+	 */
 	_getResizerByHandle( domResizeHandle ) {
-		for ( const resizer of this.resizers ) {
+		for ( const resizer of this._resizers.values() ) {
 			if ( resizer.containsHandle( domResizeHandle ) ) {
 				return resizer;
 			}
 		}
 	}
+
+	/**
+	 * Returns a resizer created for a given view element (widget element).
+	 *
+	 * @protected
+	 * @param {module:engine/view/containerelement~ContainerElement} viewElement
+	 * @returns {module:widget/widgetresize/resizer~Resizer}
+	 */
+	_getResizerByViewElement( viewElement ) {
+		return this._resizers.get( viewElement );
+	}
 }
 
+mix( WidgetResize, ObservableMixin );
+
 /**
  * Interface describing a resizer. It allows to specify the resizing host, custom logic for calculating aspect ratio, etc.
  *

+ 18 - 13
packages/ckeditor5-widget/src/widgetresize/resizer.js

@@ -119,8 +119,6 @@ export default class Resizer {
 		this._sizeUI.bindToState( this._options, this.state );
 
 		this.state.begin( domResizeHandle, this._getHandleHost(), this._getResizeHost() );
-
-		this.redraw();
 	}
 
 	/**
@@ -142,17 +140,15 @@ export default class Resizer {
 		newSize.handleHostWidth = Math.round( domHandleHostRect.width );
 		newSize.handleHostHeight = Math.round( domHandleHostRect.height );
 
+		// Handle max-width limitation.
 		const domResizeHostRect = new Rect( domHandleHost );
 
 		newSize.width = Math.round( domResizeHostRect.width );
 		newSize.height = Math.round( domResizeHostRect.height );
 
-		this.state.update( newSize );
+		this.redraw( domHandleHostRect );
 
-		// Refresh values based on the real image. Real image might be limited by max-width, and thus fetching it
-		// here will reflect this limitation.
-		this._domResizerWrapper.style.width = newSize.handleHostWidth + 'px';
-		this._domResizerWrapper.style.height = newSize.handleHostHeight + 'px';
+		this.state.update( newSize );
 	}
 
 	/**
@@ -187,8 +183,10 @@ export default class Resizer {
 
 	/**
 	 * Redraws the resizer.
+	 *
+	 * @param {module:utils/dom/rect~Rect} [handleHostRect] Handle host rectangle might be given to improve performance.
 	 */
-	redraw() {
+	redraw( handleHostRect ) {
 		// TODO review this
 		const domWrapper = this._domResizerWrapper;
 
@@ -196,21 +194,28 @@ export default class Resizer {
 			// Refresh only if resizer exists in the DOM.
 			const widgetWrapper = domWrapper.parentElement;
 			const handleHost = this._getHandleHost();
-			const clientRect = new Rect( handleHost );
+			const clientRect = handleHostRect || new Rect( handleHost );
 
 			domWrapper.style.width = clientRect.width + 'px';
 			domWrapper.style.height = clientRect.height + 'px';
 
+			const offsets = {
+				left: handleHost.offsetLeft,
+				top: handleHost.offsetTop,
+				height: handleHost.offsetHeight,
+				width: handleHost.offsetWidth
+			};
+
 			// In case a resizing host is not a widget wrapper, we need to compensate
 			// for any additional offsets the resize host might have. E.g. wrapper padding
 			// or simply another editable. By doing that the border and resizers are shown
 			// only around the resize host.
 			if ( !widgetWrapper.isSameNode( handleHost ) ) {
-				domWrapper.style.left = handleHost.offsetLeft + 'px';
-				domWrapper.style.top = handleHost.offsetTop + 'px';
+				domWrapper.style.left = offsets.left + 'px';
+				domWrapper.style.top = offsets.top + 'px';
 
-				domWrapper.style.height = handleHost.offsetHeight + 'px';
-				domWrapper.style.width = handleHost.offsetWidth + 'px';
+				domWrapper.style.height = offsets.height + 'px';
+				domWrapper.style.width = offsets.width + 'px';
 			}
 		}