Prevent lagging in DOM - javascript

So I am writing a sort of drawing script, and it works fine right now (although the code still needs to be cleaned up and there needs to be more features), but when painting too much, mousemove lags incredibly. Here is the main Javascript:
$('#canvas').on('mousedown', function(){
going = !going;
$(this).on('mousemove', function(e){
if(cursor == 'paint' && going == true){
$('.fall').each(function(){
if ($(this).css("opacity") == 0){
$(this).remove();
};
});
var ps = $('#canvas').offset().top;
var t = (e.pageY - ps - $('.fall').height()).toString() + 'px';
var l = (e.pageX - $('.fall').width()).toString() + 'px';
$('.fall').css("margin_left",l);
$('.fall').css("margin_top",t);
var doit = '<div class="fall" style="position:absolute;margin-left:' + l + ';margin-top:' + t + ';background-color:'+ color +';box-shadow: 0px 0px 5px ' + color + ';"></div>'
$('#canvas').prepend(doit);
}
else if(cursor == 'erase'){
$('.fall').mouseenter(function(){
$(this).fadeOut('fast',function(){
$(this).remove()
});
});
};
});
Essentially, when you click in the section for drawing, if the paint button is clicked, you can draw: jsfiddle.
My issue:
If you draw too much, especially with starting and stopping, it does not append enough on the mousemove do to (I assume) the DOM being overwhelmed.
Question:
What would be an efficient way to add many many divs to the DOM without creating a lag? Is this possible?
Note:
this is a personal project and I am not interested in using previously created drawing APIs

There's a lot you can do to improve performance.
The code below is a heavy refactoring of the code in the question. At first glance it might appear to be less efficient as it has about double the number of lines of the original. However, line count isn't the issue here. Two basic principles apply:
do as little DOM interaction as possible in the mousemove handler, and as much as possible on mousedown.
include a "divider circuit" to limit the number of times the mousemove handler is called. This is achieved by detaching the mousemove event handler on every call and reattaching after a short delay, conditional on the mouse still being down.
Also see comments in the code.
jQuery(function($) {
...
var $canvas = $("#canvas");
var data = {
name: 'fall'//a unique string for namespacing the muousemove event.
};
$canvas.on('mousedown', function() {
going = !going;
data.$fall = $('.fall');//this collection is created once per mousedown then managed inside mm to avoid unnecessary DOM interaction
data.mousedown = true;
data.colorCSS = {
'background-color': color,
'box-shadow': '0px 0px 5px ' + color
};
data.fallWidth = data.$fall.width();
data.fallHeight = data.$fall.height();
attachMouseMoveHandler();
}).on('mouseup', function() {
data.mousedown = false;
}).trigger('mouseup');
function attachMouseMoveHandler() {
if(data.mousedown);
$canvas.on('mousemove.' + data.name, mm);//the event is namespaced so its handler can be removed without affecting other canvas functionality
}
//The mousemove handler
function mm(e) {
if(going && cursor == 'paint') {
data.$fall.each(function() {
data.$fall = data.$fall.not(this);//manage data.$fall rather than re-form at every call of mm()
var $this = $(this);
if ($this.css("opacity") == 0) {
$this.remove();
};
});
data.$fall = data.$fall.add($('<div class="fall" />').css(data.colorCSS).prependTo($canvas)).css({
'margin-left': (e.pageX - data.fallWidth) + 'px',
'margin-top': (e.pageY - $canvas.offset().top - data.fallHeight) + 'px'
});
}
else if(cursor == 'erase') {
data.$fall.mouseenter(function() {
data.$fall = data.$fall.not(this);//manage data.$fall rather than re-form at every call of mm()
var $this = $(this).fadeOut('fast', function() {
$this.remove();
});
});
};
$canvas.off('mousemove.' + data.name);
setTimeout(attachMouseMoveHandler, 50);//adjust delay up/down to optimise performance
}
});
Tested only for syntax
I had to make a number of assumptions, chiefly concerning what becomes fixed data on mousedown. These assumptions may be incorrect, so you will most probably still have some work to do, but as long as you work inside the overall framework above, there's a good chance that your performance issues will disappear.

Related

calling functions inside functions - Trouble:function not executing

Ok. I want to apologize first because I don not even know how to title this question properly.
I will try to explain:
I have a working code for this that kind-of-works, but it is a bit messy, so I am trying to divide it into small functions.
The whole process is a simulated scrollbar:
-div id="scrollbar" represents the scrollbar track.
-div id="scrollbar"'s only child represents the scrollbar thumb.
Now. Whenever an "onmousedown" event takes place in document.documentElement, function checkAndPerform(e) gets executed.
checkAndPerform(e) gets borders of scrollbar and calls the following functions:
-checkClick(e): It checks if click has occurred inside scrollbar and returs true or false.
-perform(): If result of checkClick is true, it moves thumb to click position. Also, it scrolls down the section where the scrollbar operates (section id="ejercicios"). Finally, it adds a "mousemove" EventListener to document.documentElement, with function followMe(e) attached to it.
This followMe function calls a function that limits scroll to trackbar borders. After that it perform translation of div and scrolling of section while "mousemove" is active, and finally calls a function release(), that adds a "mouseup" EventListener to remove the followMe function after mouse button gets released.
The idea for the code was got here:
How can I retrieve all mouse coordinates between mousedown to mouseup event, where you can see in accepted answer that function called "trackPoints" gets called troubleless (as it is in my previous code).
So, here it is troubling javascript code:
function getHeight(object) {
var height = object.getBoundingClientRect().bottom - object.getBoundingClientRect().top;
return height;
}
function checkClick(e) {
console.log("x:" + e.pageX, "y:" + e.pageY);
if (e.pageX > sBLeft - 5 && e.pageX < sBRight + 5 && e.pageY < sBBottom && e.pageY > sBTop) {
adentroBar = true;
console.log("meas adentro");
} else {
adentroBar = false;
console.log("meas afuera");
}
return adentroBar;
}
function scrollLimited(e) {
if (e.pageY < sBTop) {
translateY = 0;
} else if (e.pageY > sBBottom) {
translateY = getHeight(scrollBar) - getHeight(thumb);
} else {
translateY = e.pageY - sBTop - .5 * getHeight(thumb);
}
}
function followMe(e) {
scrollLimited;
thumb.style.transform = "translate3d(0," + translateY + "px,0)";
document.getElementById('ejercicios').scrollTop = translateY * ratio;
document.documentElement.addEventListener("mouseup", release, false);
}
function perform() {
if (adentroBar === true) {
translateY = e.pageY - sBTop - getHeight(thumb) / 2;
}
thumb.style.transform = "translate3d(0," + translateY + "px,0)";
document.getElementById('ejercicios').scrollTop = translateY * ratio;
document.documentElement.addEventListener("mousemove", followMe, false);
}
function release() {
document.documentElement.removeEventListener("mousemove", followMe, false);
}
function checkAndPerform(e) {
var translateY, adentroBar, scrollBar = document.getElementById('scrollbar'),
thumb = scrollBar.getElementsByTagName("div")[0],
sBLeft = scrollBar.getBoundingClientRect().left,
sBRight = scrollBar.getBoundingClientRect().right,
sBTop = scrollBar.getBoundingClientRect().top,
sBBottom = scrollBar.getBoundingClientRect().bottom,
preg = document.getElementById('preguntas'),
ratio = preg.offsetHeight / (getHeight(scrollBar) - getHeight(thumb));
if (e.which === 1) {
checkClick;
perform;
}
}
document.documentElement.addEventListener("mousedown", checkAndPerform, false);
Also, here it is a jFiddle for it: https://jsfiddle.net/pa0exs4q/
I may provide the working code in case you find it interesting, but as i have said, it is really messy and porly written.
Problem is second function in flow (checkClick), is not even being called.
I have tried to call it as (checkClick(e)), in that case it runs, but fails to recognize variables defined above in checkAndPerform.
In my working code, everything was an only function, so i think it may be a scope problem, but I am open for any ideas.
If you have a function named myFunction, you can pass it around simply as myFunction, but to actually call it you have to write it as myFunction(). So this:
function checkAndPerform(e){
var translateY, adentroBar, scrollBar=document.getElementById('scrollbar'), thumb=scrollBar.getElementsByTagName("div")[0], sBLeft=scrollBar.getBoundingClientRect().left, sBRight=scrollBar.getBoundingClientRect().right, sBTop=scrollBar.getBoundingClientRect().top, sBBottom=scrollBar.getBoundingClientRect().bottom, preg=document.getElementById('preguntas'), ratio=preg.offsetHeight/(getHeight(scrollBar)-getHeight(thumb));
if (e.which===1){
checkClick;
perform;
}
}
Should become this:
function checkAndPerform(e){
var translateY, adentroBar, scrollBar=document.getElementById('scrollbar'), thumb=scrollBar.getElementsByTagName("div")[0], sBLeft=scrollBar.getBoundingClientRect().left, sBRight=scrollBar.getBoundingClientRect().right, sBTop=scrollBar.getBoundingClientRect().top, sBBottom=scrollBar.getBoundingClientRect().bottom, preg=document.getElementById('preguntas'), ratio=preg.offsetHeight/(getHeight(scrollBar)-getHeight(thumb));
if (e.which===1){
checkClick(e);
perform(e); // make sure to pass e and expect it in perform, otherwise it won't have access to it
}
}
Without seeing your corresponding markup, this seems to me the most likely and glaring issue in your code.

Drag and drop anywhere on screen with multiple elements of the same ID Class and Tag HTML

Hello everyone this is my first question so I might have done it wrong.
What I am trying to achieve is is have multiple <aside> elements all with the same ID Class and of course Tag be able to be moved anywhere on the screen with drag and drop characteristics. I have found a JSFiddle demonstrating the code I am basing this around that uses one aside element with the ability to be moved anywhere, but will not work when multiple elements are used. The code controlling the movement is here:
function drag_start(event) {
var style = window.getComputedStyle(event.target, null);
event.dataTransfer.setData("text/plain",
(parseInt(style.getPropertyValue("left"),10) - event.clientX) + ',' +
(parseInt(style.getPropertyValue("top"),10) - event.clientY));
}
function drag_over(event) {
event.preventDefault();
return false;
}
function drop(event) {
var offset = event.dataTransfer.getData("text/plain").split(',');
var dm = document.getElementById('dragme');
dm.style.left = (event.clientX + parseInt(offset[0],10)) + 'px';
dm.style.top = (event.clientY + parseInt(offset[1],10)) + 'px';
event.preventDefault();
return false;
}
var dm = document.getElementById('dragme');
dm.addEventListener('dragstart',drag_start,false);
document.body.addEventListener('dragover',drag_over,false);
document.body.addEventListener('drop',drop,false);
The part I am having trouble with is the drop system which requires the elements to have individual ids. I also need to have all aside elements have to same id and I don't want to use classes. I have tried and thought and searched the web for the past three days with no luck of finding an answer as all link back to this same code. I have looked into getting the index of the last element clicked but cannot find a way to get all elements with the same ID. Thanks in advanced - bybb
Update: Have broken the dnd system need help with that:
var dragindex = 0;
$(document).ready(function(){
$("aside").click(function(){
dragindex = $(this).index();
});
});
function drag_start(event) {
var style = window.getComputedStyle(event.target, null);
event.dataTransfer.setData("text/plain",
(parseInt(style.getPropertyValue("left"),10) - event.clientX) + ',' + (parseInt(style.getPropertyValue("top"),10) - event.clientY));
}
function drag_over(event) {
event.preventDefault();
return false;
}
function drop(event) {
var offset = event.dataTransfer.getData("text/plain").split(',');
var dm = document.getElementById('#winborder');
dm.style.left = (event.clientX + parseInt(offset[0],10)) + 'px';
dm.style.top = (event.clientY + parseInt(offset[1],10)) + 'px';
event.preventDefault();
return false;
}
function makewindow(content, storevar) {
storevar = document.createElement('aside');
storevar.setAttribute("dragable", "true");
storevar.setAttribute("id", "winborder");
var content = document.createElement('div');
content.setAttribute("id", "wincontent");
storevar.appendChild(content);
document.body.appendChild(storevar);
storevar.addEventListener('dragstart',drag_start,false);
document.body.addEventListener('dragover',drag_over,false);
document.body.addEventListener('drop',drop,false);
}
Have tried with and without '#' on getelementbyid
The following code will allow drag and drop; I'm sorry it doesn't follow your previous coding style.
Here is a JSFiddle showing it in action: https://jsfiddle.net/6gq7u8Lc/
document.getElementById("dragme").onmousedown = function(e) {
this.prevX = e.clientX;
this.prevY = e.clientY;
this.mouseDown = true;
}
document.getElementById("dragme").onmousemove = function(e) {
if(this.mouseDown) {
this.style.left = (Number(this.style.left.substring(0, this.style.left.length-2)) + (e.clientX - this.prevX)) + "px";
this.style.top = (Number(this.style.top.substring(0, this.style.top.length-2)) + (e.clientY - this.prevY)) + "px";
}
this.prevX = e.clientX;
this.prevY = e.clientY;
}
document.getElementById("dragme").onmouseup = function() {
this.mouseDown = false;
}
On a side note, you won't be able to give them all the same id. The DOM doesn't support such a thing. If you want to add multiple elements of the same type, I would suggest a 'container' parent div, adding the elements as children of the div, and iterating through the .children attribute to access each.
Don't know if you want to use Jquery or not, but I know it's way simpler than the raw javascript solution. I've been searching the internet and Stack Overflow for a simple drag-and-drop anywhere on a web page solution, but I couldn't find one that worked. The other answers to this question have been weird for me. They sometimes work, sometimes don't. However, a friend suggested using the Jquery version, and wow, it's so much simpler. Just one line of code!
var dragme = document.getElementsByClassName("dragme");
for (var i = 0; i < dragme.length; i++) {
$(dragme[i]).draggable();
}
.dragme {
cursor: move;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
<h1 class="dragme">Try dragging me anywhere on this sample!</h1>
<h2 class="dragme">Drag me, too!</h2>
IMPORTANT NOTE: This code does not work with just Jquery, it also requires the Jquery UI file.
If the above sample does not work for you, please comment the error it show below. Note that this works for an unlimited amount of elements. This specific example is for elements with the class "dragme", so just replace that with your own class. For one element, it is a single line of code: $("#yourid").draggable();.
This code works for me on Google Chrome. Hope this helps anyone coming on to this page late, like me!

How can I implement a callback after layer.destroyChilden()

This is for homework and it has an annoying bug I can't solve after hours of struggle.
http://canvaseu.chrisloughnane.net/
When on the eu map clicking a country path fires the bound event.
This event destroys the layers children and loads the country. BUT if you click and quickly move the mouse and let go the button the country loads but the country path from the eu remains. This is best demonstrated with Spain as shown in the screen grab.
I am hoping a callback after mapLayer.destroyChildren(); to then call the load function would solve my problem.
This can be a little difficult to replicate.
I'm sure my control is too tied up with my view but I haven't been able to see a solution to separate them neatly.
**** EDIT ****
I came up with a solution that works partially but I think this is terrible hack code, I added to the mousedown binding down = true; and added checks to the mouseout binding, please see below.
What I think is happening is when you move the mouse and let the button go very quickly the mouseover binding is over riding the mouseup.
This solution isn't ideal, after loading a number of countries and mouseover on regions the canvas response slows down.
Event Binding
path.on('mousedown touchstart', function()
{
down = true;
this.setStroke('red');
this.moveTo(topLayer);
/****
* Handle if the path we are displaying in canvas is the eu
* to allow selection and load of country path point data.
*/
if (associativeCountryArray[lastCountry].getText() == 'eu')
{
associativeCountryArray[lastCountry].setFill('#bbb');
associativeCountryArray[lastCountry].setFontStyle('normal');
countrynames[lastCountry].selected = false;
this.moveTo(mapLayer);
mapLayer.destroyChildren();
lastCountry = this.getName();
countrynames[this.getName()].selected = true;
associativeCountryArray[this.getName()].setFill("rgb(" + r + "," + g + "," + b + ")");
associativeCountryArray[this.getName()].setFontStyle('bold');
loadPaths(this.getName().replace(/\s/g, ''));
countryNameLayer.draw();
}
else
{
window.open('https://www.google.com/search?q=' + this.getName(),'_blank');
}
topLayer.drawScene();
});
path.on('mouseout', function()
{
if(!down)
{
document.body.style.cursor = 'default';
this.setFill('#eee');
this.setStrokeWidth(settings.strokewidthstart);
/****
* On hover, only change colour of country names around edge of canvas if we are on the 'eu' map
*/
if (lastCountry == 'eu')
{
associativeCountryArray[this.getName()].setFill('#bbb');
associativeCountryArray[this.getName()].setFontStyle('normal');
}
this.setStroke('#555');
this.moveTo(mapLayer);
writeMessage('');
topLayer.draw();
countryNameLayer.draw();
}
else
{
down = false;
}
});
path.on('mouseup touchend', function()
{
this.setStroke('black');
topLayer.drawScene();
down = false;
});
Like this:
Send the container (group,layer) you want to clear and the callback you want triggered.
function myDestroyChildren(container,callback) {
var children = container.getChildren();
while(children.length > 0) {
children[0].destroy();
}
callback();
}

initialize mousemove event only after click

I am writing a plugin that creates a select marquee box when the mouse is clicked on a canvas div. So far i've written the plugin with the click() and mousemove() events adjacent to each other in the code (i dont know of any way to embed events inside other events). But this becomes a problem when the mouse moves over the canvas before a click. Does anyone know how to initialize a mousemove handler with a click?
Here is the code i have written so far:
$.fn.createBox = function(id) {
$(id).css({
cursor:'crosshair'
});
$(id).click(function(e) {
var clickLocX = e.pageX;
var clickLocY = e.pageY;
$('<div>').attr({
'class':'newBox',
'ctr':'on'
})
.css({
top:clickLocY,
left:clickLocX
})
.appendTo(id);
});
//Mousemove must be initialized only AFTER the click. HOW TO DO THIS?
$(id).mousemove(function(e){
var XpageCoord = e.pageX;
var YpageCoord = e.pageY;
window.Xloc = XpageCoord;
window.Yloc = YpageCoord;
var boxOffset = $('.newBox').offset();
var boxHeight = YpageCoord - boxOffset.top;
var boxWidth = XpageCoord - boxOffset.left;
$('.newBox').css({
height:boxHeight + 'px',
width:boxWidth + 'px'
});
});
}
Just set the mousemove handler from within the click handler and use a flag to determine whether or not it has already been set to avoid adding it multiple times.
EDIT: An example
assignedMoveHandler = false;
$(id).click(function(e) {
// other stuff...
if(!assignedMoveHandler) {
$(id).mousemove(function(e) {
// mouse move handling code...
});
assignedMoveHandler = true;
}
}
I use a global here which you may or may not care about, I lookup the jquery object twice, etc., so it could be cleaned up a bit, but this is the general idea. When the click handler fires you check to see if you've assigned a mousemove handler yet. If not, assign it.

JavaScript or jQuery image carousel/filmstrim

I am looking for some native JavaScript, or jQuery plugin, that meets the following specification.
Sequentially moves over a set of images (ul/li)
Continuous movement, not paging
Appears infinite, seamlessly restarts at beginning
Ability to pause on hover
Requires no or minimal additional plugins
I realize this sounds simple enough. But I have looked over the web and tried Cycle and jCarousel/Lite with no luck. I feel like one should exist and wanted to pose the question before writing my own.
Any direction is appreciated. Thanks.
you should check out Nivo Slider, I think with the right configuration you can it to do what you want.
You can do that with the jQuery roundabout plugin.
http://fredhq.com/projects/roundabout/
It might require another plugin.
Both answers by MoDFoX and GSto are good. Usually I would use one of these, but these plugins didn't meet the all the requirements. In the end this was pretty basic, so I just wrote my own. I have included the JavaScript below. Essentially it clones an element on the page, presumably a ul and appends it to the parent container. This in effect allows for continuous scrolling, right to left, by moving the element left and then appending it once out of view. Of course you may need to tweak this code depending on your CSS.
// global to store interval reference
var slider_interval = null;
var slider_width = 0;
var overflow = 0;
prepare_slider = function() {
var container = $('.sliderGallery');
if (container.length == 0) {
// no gallery
return false;
}
// add hover event to pause slider
container.hover(function() {clearInterval(slider_interval);}, function() {slider_interval = setInterval("slideleft()", 30);});
// set container styles since we are absolutely positioning elements
var ul = container.children('ul');
container.css('height', ul.outerHeight(true) + 'px');
container.css('overflow', 'hidden')
// set width and overflow of slider
slider_width = ul.width();
overflow = -1 * (slider_width + 10);
// set first slider attributes
ul.attr('id', 'slider1');
ul.css({"position": "absolute", "left": 0, "top": 0});
// clone second slider
var ul_copy = ul.clone();
// set second slider attributes
ul.attr('id', 'slider2');
ul_copy.css("left", slider_width + "px");
container.append(ul_copy);
// start time interval
slider_interval = setInterval("slideleft()", 30);
}
function slideleft() {
var copyspeed = 1;
var slider1 = $('#slider1');
var slider2 = $('#slider2');
slider1_position = parseInt(slider1.css('left'));
slider2_position = parseInt(slider2.css('left'));
// cross fade the sliders
if (slider1_position > overflow) {
slider1.css("left", (slider1_position - copyspeed) + "px");
}
else {
slider1.css("left", (slider2_position + slider_width) + "px");
}
if (slider2_position > overflow) {
slider2.css("left", (slider2_position - copyspeed) + "px");
}
else {
slider2.css("left", (slider1_position + slider_width) + "px");
}
}

Categories