Improved tooltip setup performance
authorAlexander Ebert <ebert@woltlab.com>
Fri, 1 Sep 2017 10:18:16 +0000 (12:18 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Fri, 1 Sep 2017 10:18:16 +0000 (12:18 +0200)
The live collection of `getElementsByClass()` shows a bad performance
when a large list of elements gets remove one-by-one.

It took around 1 second to process 4k elements and stripping the class
from each one. Using `querySelectorAll` instead does the same job in
less than 40ms.

The check against `_elements` is still in place, because it performs
pretty well with large sets of DOM elements (tested with ~16k elements)
and allows us to return early without invoking the costly
`querySelectorAll`, which even without results hit with 0,2 seconds
during tests.

wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Tooltip.js

index 09f32ec57e941bc0f602ddc9c4a72033922e5a13..5fc7f59142dba0a1bbe520fe7f97ac02c35ce658 100644 (file)
@@ -9,6 +9,8 @@
 define(['Environment', 'Dom/ChangeListener', 'Ui/Alignment'], function(Environment, DomChangeListener, UiAlignment) {
        "use strict";
        
+       var _callbackMouseEnter = null;
+       var _callbackMouseLeave = null;
        var _elements = null;
        var _pointer = null;
        var _text = null;
@@ -49,6 +51,9 @@ define(['Environment', 'Dom/ChangeListener', 'Ui/Alignment'], function(Environme
                        
                        _elements = elByClass('jsTooltip');
                        
+                       _callbackMouseEnter = this._mouseEnter.bind(this);
+                       _callbackMouseLeave = this._mouseLeave.bind(this);
+                       
                        this.init();
                        
                        DomChangeListener.add('WoltLabSuite/Core/Ui/Tooltip', this.init.bind(this));
@@ -59,21 +64,23 @@ define(['Environment', 'Dom/ChangeListener', 'Ui/Alignment'], function(Environme
                 * Initializes tooltip elements.
                 */
                init: function() {
-                       var element, title;
-                       while (_elements.length) {
-                               element = _elements[0];
+                       if (_elements.length === 0) {
+                               return;
+                       }
+                       
+                       elBySelAll('.jsTooltip', undefined, function (element) {
                                element.classList.remove('jsTooltip');
                                
-                               title = elAttr(element, 'title').trim();
+                               var title = elAttr(element, 'title').trim();
                                if (title.length) {
                                        elData(element, 'tooltip', title);
                                        element.removeAttribute('title');
                                        
-                                       element.addEventListener('mouseenter', this._mouseEnter.bind(this));
-                                       element.addEventListener('mouseleave', this._mouseLeave.bind(this));
-                                       element.addEventListener(WCF_CLICK_EVENT, this._mouseLeave.bind(this));
+                                       element.addEventListener('mouseenter', _callbackMouseEnter);
+                                       element.addEventListener('mouseleave', _callbackMouseLeave);
+                                       element.addEventListener(WCF_CLICK_EVENT, _callbackMouseLeave);
                                }
-                       }
+                       });
                },
                
                /**