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