Hi all. I have some performance tips to share. I had been having problems with the overall performance of the drag/drop objects. This is due largely to the fact that my project really pushes the limits (potentially thousands of draggables and hundreds of droppables in the document at a time, although that is the extreme case). Something I discovered is that the performance of dragging goes WAY down as you add draggables and (even more so) as you add droppables. This might be a "duh" statement, but I had originally thought the problem was more in just the shear number of objects I was creating on the client (sitting in memory), and not necessarily the fault of scriptaculous or any one script. However, with every mousemove event, the entire draggables (and droppables) collections are spun through checking for all sorts of things, which REALLY bogs down performance once you start trying to add any significant number of draggables or droppables. Here are a couple of optimizations I found that seem to work pretty good. The first one is along the lines of the "lazy loading" pattern. In other words, only create the draggables (or droppables) when they are likely to be needed. For draggables I do this with a mouseover event. You can pretty safely assume that an element does not need to be made draggable if the mouse never passes over it. So only create the draggable on a mouseover. For droppables, you can wait even longer to create them (and be more likely to be correct in assuming that a droppable must be created), but the detection of the "creation condition" is a little more complicated. You only need to create a droppable if the mouse passes over the droppable element AND you are currently dragging a draggable object (AND only if that draggable is of a specific class if the droppable is to only accept certain draggables,). So below is how you can accomplish this type of "lazy loading" of draggables and droppables. Notice that for droppables, it is much more of a task since a mouseover event will NOT register if there is an element between the mouse and the element-to-become-a-droppable, which will almost always be the case since you are dragging something. // My examples assume you are using objects, and creating your draggables/droppables from within an object. If you want to do this from the global scope (not within an object), just remove the appropriate "this" references. DRAGGABLE LAZY LOADING... For draggables, a simple straight forward "mouseover" listener will suffice. Remember, in your object remember to have a dispose() method of some sort for proper cleanup of event handlers and unregistering draggables. Notice, if the element is never moused-over that the "mouseover" listener will never get removed, so you need to handle this condition in your objects. this.createDraggableListener = function() { this.draggable = new Draggable($("someElement"), {...options...} ); //remove this listener once the draggable has been created (no longer needed) Event.stopObserving($("someElement"), "mouseover", this.createDraggableListener); this.createDraggableListener = null; }; Event.observe($("someElement"), "mouseover", this.createDraggableListener); DROPPABLE LAZY LOADING... This is much more complicated, and asynchronous execution is really required to avoid UI blocking (the call to Position.within() is a major performance hog, which is the main reason why having a lot of droppables slows your application down in the first place). You may still notice intermittent glitches while dragging something around the screen if you have many many many droppables. But, using this method of only creating the droppables when needed, it will be MUCH faster than if you just brute-force create all those droppables on page load. NOTE: I made this example create a droppable no matter what draggable is dragged over it, but you can add a check for Draggables.activeDraggable.element.className == "someClassName" if you only want the droppable created for certain draggables (put that check at the end of the initial if statement). Of course you can check for multiple classnames using the prototype functions if you need (or add any other conditions there for your application) this.createDroppableListener = function(evt) { if (!this.checking && evt && Draggables.activeDraggable) { this.checking = true; var pos = [Event.pointerX(evt), Event.pointerY(evt)]; setTimeout(function() { if (Position.within($("elementToBecomeDroppable"), pos[0], pos[1])) { Droppables.add($("elementToBecomeDroppable"), {...options...} ); //remove this listener once the droppable has been created (no longer needed) Event.stopObserving(document, "mousemove", this.createDroppableListener); this.createDroppableListener = null; } this.checking = false; }.bind(this), 1); } }.bindAsEventListener(this); Event.observe(document, "mousemove", this.createDroppableListener); The other optimization is more advanced and involves hacking the prototype and scriptaculous core scripts. I''m not going to go into lengthy details about this, but it has to do with unrolling costly loops (namely all the checks that happen as you drag something around the screen). Basically, you can use the following implementation of Duff''s Device (http://www.websiteoptimization.com/speed/10/) to unroll loops instead of using the ".each" command. But like I said, this is advanced and modifying the core scripts is not recommended unless you know what you are doing. (I''m really only including this for those that, like me, need to squeeze the most performance out of your apps at any cost). DUFF''S DEVICE... The main benefit of this function is in recursive functions, but it also works for general loop unrolling. It does complicate your code a bit, so use at your own risk. Notice, where testVal is being incremented you would add your function calls. For more information about loop unrolling (and general performance tips, check out http://home.earthlink.net/~kendrasg/info/js_opt/ ) function duffFasterLoop8(iterations) { // Original JS Implementation by Jeff Greenberg 2/2001 // http://home.earthlink.net/~kendrasg/info/js_opt/ // (fast duff''s device) from an anonymous donor to Jeff Greenberg''s site // (faster duff''s defice) by Andrew King 8/2002 for WebSiteOptimization.com // bug fix (for iterations<8) by Andrew B. King April 12, 2003 var testVal=0; var n = iterations % 8; if (n>0) { do { testVal++; } while (--n); // n must be greater than 0 here } n = parseInt(iterations / 8); if (n>0) { // if iterations < 8 an infinite loop, added for safety in second printing do { testVal++; testVal++; testVal++; testVal++; testVal++; testVal++; testVal++; testVal++; } while (--n); // n must be greater than 0 here also } } Sincerely, Ryan Gahl Design Engineer Camtronics Medical Systems (an Emageon Company) Ryan.gahl-nlycWCgr5/vuufBYgWm87A@public.gmane.org 262-369-3251 The information transmitted in this electronic mail is intended only for the person or entity to which it is addressed and may contain confidential, proprietary, and/or privileged material. Any review, retransmission, dissemination or other use of, or taking of any action in reliance upon, this information by persons or entities other than the intended recipient is prohibited. If you received this in error, please contact the sender and delete the material from all computers. _______________________________________________ Rails-spinoffs mailing list Rails-spinoffs-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org http://lists.rubyonrails.org/mailman/listinfo/rails-spinoffs
In the first block (draggable lazy loading), I forgot to add ".bind(this)" to the end of the definition of the "this.createDraggableListener" variable... sorry. ________________________________ From: rails-spinoffs-bounces-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org [mailto:rails-spinoffs-bounces-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org] On Behalf Of Ryan Gahl Sent: Thursday, February 09, 2006 4:56 PM To: rails-spinoffs-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org Subject: [Rails-spinoffs] Draggables and Droppable performance (tips) Hi all. I have some performance tips to share. I had been having problems with the overall performance of the drag/drop objects. This is due largely to the fact that my project really pushes the limits (potentially thousands of draggables and hundreds of droppables in the document at a time, although that is the extreme case). Something I discovered is that the performance of dragging goes WAY down as you add draggables and (even more so) as you add droppables. This might be a "duh" statement, but I had originally thought the problem was more in just the shear number of objects I was creating on the client (sitting in memory), and not necessarily the fault of scriptaculous or any one script. However, with every mousemove event, the entire draggables (and droppables) collections are spun through checking for all sorts of things, which REALLY bogs down performance once you start trying to add any significant number of draggables or droppables. Here are a couple of optimizations I found that seem to work pretty good. The first one is along the lines of the "lazy loading" pattern. In other words, only create the draggables (or droppables) when they are likely to be needed. For draggables I do this with a mouseover event. You can pretty safely assume that an element does not need to be made draggable if the mouse never passes over it. So only create the draggable on a mouseover. For droppables, you can wait even longer to create them (and be more likely to be correct in assuming that a droppable must be created), but the detection of the "creation condition" is a little more complicated. You only need to create a droppable if the mouse passes over the droppable element AND you are currently dragging a draggable object (AND only if that draggable is of a specific class if the droppable is to only accept certain draggables,). So below is how you can accomplish this type of "lazy loading" of draggables and droppables. Notice that for droppables, it is much more of a task since a mouseover event will NOT register if there is an element between the mouse and the element-to-become-a-droppable, which will almost always be the case since you are dragging something. // My examples assume you are using objects, and creating your draggables/droppables from within an object. If you want to do this from the global scope (not within an object), just remove the appropriate "this" references. DRAGGABLE LAZY LOADING... For draggables, a simple straight forward "mouseover" listener will suffice. Remember, in your object remember to have a dispose() method of some sort for proper cleanup of event handlers and unregistering draggables. Notice, if the element is never moused-over that the "mouseover" listener will never get removed, so you need to handle this condition in your objects. this.createDraggableListener = function() { this.draggable = new Draggable($("someElement"), {...options...} ); //remove this listener once the draggable has been created (no longer needed) Event.stopObserving($("someElement"), "mouseover", this.createDraggableListener); this.createDraggableListener = null; }; Event.observe($("someElement"), "mouseover", this.createDraggableListener); DROPPABLE LAZY LOADING... This is much more complicated, and asynchronous execution is really required to avoid UI blocking (the call to Position.within() is a major performance hog, which is the main reason why having a lot of droppables slows your application down in the first place). You may still notice intermittent glitches while dragging something around the screen if you have many many many droppables. But, using this method of only creating the droppables when needed, it will be MUCH faster than if you just brute-force create all those droppables on page load. NOTE: I made this example create a droppable no matter what draggable is dragged over it, but you can add a check for Draggables.activeDraggable.element.className == "someClassName" if you only want the droppable created for certain draggables (put that check at the end of the initial if statement). Of course you can check for multiple classnames using the prototype functions if you need (or add any other conditions there for your application) this.createDroppableListener = function(evt) { if (!this.checking && evt && Draggables.activeDraggable) { this.checking = true; var pos = [Event.pointerX(evt), Event.pointerY(evt)]; setTimeout(function() { if (Position.within($("elementToBecomeDroppable"), pos[0], pos[1])) { Droppables.add($("elementToBecomeDroppable"), {...options...} ); //remove this listener once the droppable has been created (no longer needed) Event.stopObserving(document, "mousemove", this.createDroppableListener); this.createDroppableListener = null; } this.checking = false; }.bind(this), 1); } }.bindAsEventListener(this); Event.observe(document, "mousemove", this.createDroppableListener); The other optimization is more advanced and involves hacking the prototype and scriptaculous core scripts. I''m not going to go into lengthy details about this, but it has to do with unrolling costly loops (namely all the checks that happen as you drag something around the screen). Basically, you can use the following implementation of Duff''s Device (http://www.websiteoptimization.com/speed/10/) to unroll loops instead of using the ".each" command. But like I said, this is advanced and modifying the core scripts is not recommended unless you know what you are doing. (I''m really only including this for those that, like me, need to squeeze the most performance out of your apps at any cost). DUFF''S DEVICE... The main benefit of this function is in recursive functions, but it also works for general loop unrolling. It does complicate your code a bit, so use at your own risk. Notice, where testVal is being incremented you would add your function calls. For more information about loop unrolling (and general performance tips, check out http://home.earthlink.net/~kendrasg/info/js_opt/ ) function duffFasterLoop8(iterations) { // Original JS Implementation by Jeff Greenberg 2/2001 // http://home.earthlink.net/~kendrasg/info/js_opt/ // (fast duff''s device) from an anonymous donor to Jeff Greenberg''s site // (faster duff''s defice) by Andrew King 8/2002 for WebSiteOptimization.com // bug fix (for iterations<8) by Andrew B. King April 12, 2003 var testVal=0; var n = iterations % 8; if (n>0) { do { testVal++; } while (--n); // n must be greater than 0 here } n = parseInt(iterations / 8); if (n>0) { // if iterations < 8 an infinite loop, added for safety in second printing do { testVal++; testVal++; testVal++; testVal++; testVal++; testVal++; testVal++; testVal++; } while (--n); // n must be greater than 0 here also } } Sincerely, Ryan Gahl Design Engineer Camtronics Medical Systems (an Emageon Company) Ryan.gahl-nlycWCgr5/vuufBYgWm87A@public.gmane.org 262-369-3251 The information transmitted in this electronic mail is intended only for the person or entity to which it is addressed and may contain confidential, proprietary, and/or privileged material. Any review, retransmission, dissemination or other use of, or taking of any action in reliance upon, this information by persons or entities other than the intended recipient is prohibited. If you received this in error, please contact the sender and delete the material from all computers. _______________________________________________ Rails-spinoffs mailing list Rails-spinoffs-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org http://lists.rubyonrails.org/mailman/listinfo/rails-spinoffs
I have run into a problem that I have never created before. I am using the scriptaculous inPlaceEditor If I hit the cancel button, and rehit the editor it then creates two textboxes, and if I repeat three input boxes, etc.! Must be stupid simple, but I AM STUMPED! DECO
Am Donnerstag, 9. Februar 2006 23:56 schrieb Ryan Gahl:> Hi all. I have some performance tips to share. I had been having > problems with the overall performance of the drag/drop objects. This is > due largely to the fact that my project really pushes the limits > (potentially thousands of draggables and hundreds of droppables in the > document at a time, although that is the extreme case).[...] Hi Ryan, quite an interesting read, thanks alot for sharing your tips. I''ve been thinking about registering the Draggables during the onmouseover event myself, but wasn''t sure how to do it right. Your examples are very helpful. Best Regards -- Dirk Eschler zeitform Internet Dienste mailto:eschler-zc1r9W/44D4b1SvskN2V4Q@public.gmane.org Fraunhoferstraße 5 PGP S/MIME: http://key.zeitform.de/ap 64283 Darmstadt, Germany Tel./Fax: +49 (0) http://www.zeitform.de