Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Commit

Permalink
Merge pull request #108 from ckeditor/t/ckeditor5-image/310
Browse files Browse the repository at this point in the history
Other: Improved the resizer performance. Closes ckeditor/ckeditor5#5191.
  • Loading branch information
Reinmar committed Oct 16, 2019
2 parents 046cbb5 + 49b0a25 commit 1d1de77
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 36 deletions.
105 changes: 82 additions & 23 deletions src/widgetresize.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 {
/**
Expand All @@ -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
Expand All @@ -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() {
Expand All @@ -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.
*
Expand Down
31 changes: 18 additions & 13 deletions src/widgetresize/resizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

/**
Expand All @@ -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 );
}

/**
Expand Down Expand Up @@ -187,30 +183,39 @@ 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;

if ( existsInDom( domWrapper ) ) {
// 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';
}
}

Expand Down

0 comments on commit 1d1de77

Please sign in to comment.