First off, the whole code is here, on pastebin.
No jquery or alikes are employed!
I have a HTML with a DIV created using document.createElement("div") and plugged into the page with document.getElementById('p').appendChild(d);
The new DIV is provided with a bunch of event handlers:
var d = document.createElement('div');
d.className = 'rect';
d.addEventListener('mousedown', catch_box, false);
d.addEventListener('mouseup', release_box, false);
d.addEventListener('mouseout', release_box, false);
d.addEventListener('mousemove', mouse_move_box, false);
d.addEventListener(wheel_hook, scroll_box, false);
document.getElementById('p').appendChild(d);
This works fine:
// move the box around on 'mousemove'
var lPos = cache.left + m_speed * cache.h_dir,
tPos = cache.top + m_speed * cache.v_dir,
ok = (lPos >= 0) && ((lPos + cache.width) <= max_width) &&
(tPos >= 0) && ((tPos + cache.height) <= max_height);
if (ok) {
box.style.left = lPos + 'px';
box.style.top = tPos + 'px';
}
but this doesn't:
// resize the box
var hSize = cache.width + s_speed * cache.h_dir,
vSize = cache.height + s_speed * cache.v_dir,
ok = (hSize >= min_width) &&
((cache.left + hSize) <= max_width) &&
(vSize >= min_height) &&
((cache.top + vSize) <= max_height);
if (ok) {
box.setAttribute('style', 'width:'+hSize+'px;height:'+vSize+'px;');
box.style.width = hSize + 'px';
box.style.height = vSize + 'px';
}
The box.style.width and box.style.height may only grow :(
The question is: WHY?? And how to work it around?
I have a Javascript animation at http://dev17.edreamz3.com/css/
All code works, however, there are performance problems. on Desktop, its good, On mobile things are so slow that it's unusable. I want to optimize the animation so that it runs smoothly on mobile. It can take 20 seconds or more for the animation to render.
Right now the way the code is designed is in js/anim.js there is a render() function that gets executed every time a scroll event happens. The problem is that this routine is not efficient, that's what I think of. Each time render() executes it loops through all the paths and sections of the maze and redraws them, is there any alternative way or a strategy to get it working both on mobile as well as desktop.
var offPathTime = 1000;
window.offSection = -1;
function render() {
// var top = ($window.scrollTop() + (0.4 * $window.height())) / window.scale;
var top = ($('.parent-div').scrollTop() + (0.4 * $('.parent-div').height())) / window.scale;
top -= 660;
top /= mazeSize.h;
if (window.offSection != -1) {
$body.addClass("blockScroll");
$('.parent-div').addClass("blockScroll");
// var wtop = $window.scrollTop() / window.scale;
var wtop = $('.parent-div').scrollTop() / window.scale;
wtop -= 660;
wtop /= mazeSize.h;
var $offSection = $("#offSection" + window.offSection);
var $section = $("#section" + window.offSection);
$(".section").removeClass("sectionActive");
$offSection.addClass("sectionActive");
$section.addClass("sectionActive");
var sTop = 200 -(mazeSize.h * (window.offSections[window.offSection].cy - wtop));
$container.animate({
left: 290 -(mazeSize.w * window.offSections[window.offSection].cx) + "px",
top: sTop + "px"
}, offPathTime);
// Path
var lr = offPaths[window.offSection].x1 > offPaths[window.offSection].x0;
var dx = Math.abs(offPaths[window.offSection].x1 - offPaths[window.offSection].x0);
var dashw = (dx * mazeSize.w) | 0;
$offPaths[window.offSection].css("width", "0px");
$offPaths[window.offSection].show();
if (lr) {
$offPaths[window.offSection].animate({
width: dashw + "px"
}, offPathTime);
} else {
var x0 = offPaths[window.offSection].x0 * mazeSize.w;
var x1 = offPaths[window.offSection].x1 * mazeSize.w;
$offPaths[window.offSection].css("left", x0 + "px");
$offPaths[window.offSection].animate({
width: dashw + "px",
left: x1 + "px"
}, offPathTime);
}
return;
}
$body.removeClass("blockScroll");
$('.parent-div').removeClass("blockScroll");
$(".offPath").hide();
if ($container.css("top") != "0px") {
$container.animate({
left: "-1550px",
top: "0px"
}, 500);
}
var pathIdx = -1;
var path0 = paths[0];
var path1;
var inPath = 0;
var i;
var curTop = 0;
var found = false;
for (i=0; i<paths.length; i++) {
var top0 = (i == 0) ? 0 : paths[i-1].y;
var top1 = paths[i].y;
if (top >= top0 && top < top1) {
pathIdx = i;
path1 = paths[i];
inPath = (top - top0) / (top1 - top0);
found = true;
if (i > 0) {
var dy = paths[i].y - paths[i-1].y;
var dx = paths[i].x - paths[i-1].x;
var vert = dx == 0;
if (vert)
$paths[i-1].css("height", (dy * mazeSize.h * inPath) + "px");
$paths[i-1].show();
}
} else if (top >= top0) {
path0 = paths[i];
var dy = paths[i].y - top0;
var vert = dy != 0;
if (i > 0) {
if (vert)
$paths[i-1].css("height", (dy * mazeSize.h) + "px");
$paths[i-1].show();
}
} else {
if (i > 0) {
$paths[i-1].hide();
}
}
curTop = top1;
}
// Check for an active section
$(".section").removeClass("sectionActive");
var section;
for (i=0; i<sections.length; i++) {
var d = Math.abs(sections[i].cy - (top - 0.05));
if (d < 0.07) {
var $section = $("#section" + i);
$section.addClass("sectionActive");
}
}
}
1) At the very least - assign all DOM objects to variables outside of the function scope. Like this:
var $parentDiv = $('.parent-div');
var $sections = $(".section");
...
function render() {
...
2) Also you should probably stop animation before executing it again, like this:
$container.stop(true).animate({
...
If you are running render() function on scroll - it will run many times per second. stop() helps to prevent it somewhat.
3) If it will not be sufficient - you can switch from jQuery to Zepto(jQuery-like api, but much faster and uses css transitions for animations) or to Velocity(basically drop-in replacement for jQuery $.animate and much faster than original) or even to GSAP - much more work obviously, but it is very fast and featured animation library.
I have created a range slider where I am displaying a bubble with slider value within it.
( This example is based on https://css-tricks.com/value-bubbles-for-range-inputs/, I am simply attempted to recreate based on JS)
Here is a function that is suppose to make the bubble move along with the slider:
function displayValue(event) {
var rangeInput = document.getElementById('myRange');
var width = rangeInput.offsetWidth;
var min = rangeInput.getAttribute('min');
var max = rangeInput.getAttribute('max');
var newPoint = (event.target.value - min)/(max - min);
var offset = -1.3;
var newPlace;
if (newPoint < 0) {
newPlace = 0;
} else if (newPoint > 1 ) {
newPlace = width;
} else {
newPlace = width * newPoint + offset;
offset -= newPoint;
}
var outputElement = document.getElementById('myOutput');
outputElement.value = event.target.value;
outputElement.style.left = newPlace;
outputElement.style.marginLeft = offset = "%";
}
This function only partially works (as in the value insider the bubble updates but not the position)
Why is the sldier failing to update it's position ?
http://plnkr.co/edit/lt99U0uvMkPsibY54GAU
I found couple of anomalies in your code which might be typos.
Instead of:
outputElement.style.left = newPlace;
outputElement.style.marginLeft = offset = "%";
Try:
outputElement.style.left = newPlace + 'px';
outputElement.style.marginLeft = offset + "%";
I'm attempting to output on a page multiple 'labels' over an image using absolute positioned divs. Each of these divs has a unique number and are placed according to an x and y position on the map (these are percentage based so the image may be scaled).
As some of these labels may overlap, I need a way to either stop them from overlapping, or to essentially 'bump' them off eachother so they no longer overlap. (At this point, it doesn't matter if they are not in their correct position as long as they are near enough as there is a separate 'Pin' view).
They need to stay within the confines of their container and not overlap with eachother.
HTML:
<div id="labelzone">
<div class="label" style="left:0%;top:8%">001</div>
<div class="label" style="left:0%;top:11%">002</div>
<div class="label" style="left:1%;top:10%">003</div>
</div>
CSS:
#labelzone{
float:left;
width:500px;
height:500px;
border: 1px solid black;
position: relative;
}
.label{
position:absolute;
border:1px solid black;
background-color:white;
}
Jsfiddle: https://jsfiddle.net/79cco1oy/
There's a simple example of what I have as an output, these pins could be placed anywhere and there is no limit to how many is on the page, however there shouldn't be any occasion where there are too many to fit in the area.
I'm toying around with doing some form of collision detection and currently attempting to figure out an algorithm of some sort to get them to no longer overlap, and ensure they also don't overlap another item.
My solution is a bit more object oriented.
One object (LabelPool) will contain labels and will be in charge of storing and accomodating them so that they don't collide. You can customize the x/y values that you want to add/substract of the Label's positions in order to avoid their collision. The other object (Label) defines a Label and has some convenient methods. The collision algorithm that I used in LabelPool was taken from this post
var Label = function ($el) {
var position = $el.position(),
width = $el.outerWidth(true),
height = $el.outerHeight(true);
this.getRect = function () {
return {
x: position.left,
y: position.top,
width: width,
height: height
};
};
this.modifyPos = function (modX, modY) {
position.top += modY;
position.left += modX;
updatePos();
};
function updatePos() {
$el.css({
top: position.top,
left: position.left
});
}
};
var LabelPool = function () {
var labelPool = [];
function collides(a, b) {
return !(((a.y + a.height) < (b.y)) || (a.y > (b.y + b.height)) || ((a.x + a.width) < b.x) || (a.x > (b.x + b.width)));
}
function overlaps(label) {
var a = label.getRect();
return labelPool.some(function (other) {
return collides(a, other.getRect());
});
}
this.accomodate = function (label) {
while (labelPool.length && overlaps(label)) {
label.modifyPos(0, 1);// You can modify these values as you please.
}
labelPool.push(label);
};
};
var labelPool = new LabelPool;
$(".label").each(function (_, el) {
labelPool.accomodate(new Label($(el)));
});
Here's the fiddle.
Hope it helps.
Using js and jquery, you can find a basic collision engine based on left/top abs position and size of the label.
https://jsfiddle.net/Marcassin/79cco1oy/6/
Every time you want to add a Label, you check if the positionning is overlaping any existing div, in this case, you translate the new Label to position. This operation may not be the most beautiful you can find, there can be a long process time in case of lots of labels.
$(document).ready (function () {
addLabel (0, 8);
addLabel (0, 11);
addLabel (1, 10);
addLabel (2, 7);
});
function addLabel (newLeft, newTop)
{
var newLab = document.createElement ("div");
newLab.className = "label";
$(newLab).css({"left": newLeft+"%", "top": newTop + "%"});
var labels = $("#labelzone > div");
newLab.innerHTML = "00" + (labels.length + 1); // manage 0s
$("#labelzone").append (newLab);
var isCollision = false;
var cpt = 1;
do
{
isCollision = false;
$(labels).each (function () {
if (! isCollision && collision (this, newLab))
isCollision = true;
});
if (isCollision)
$(newLab).css({"left": (newLeft + cpt++) + "%",
"top": (newTop + cpt++) + "%"});
} while (isCollision);
}
function isInside (pt, div)
{
var x = parseInt($(div).css("left"));
var y = parseInt($(div).css("top"));
var w = $(div).width () + borderWidth;
var h = $(div).height ();
if (pt[0] >= x && pt[0] <= x + w &&
pt[1] >= y && pt[1] <= y + h)
return true;
return false;
}
function collision (div1, div2)
{
var x = parseInt($(div1).css("left"));
var y = parseInt($(div1).css("top"));
var w = $(div1).width () + borderWidth;
var h = $(div1).height ();
var pos = [x, y];
if (isInside (pos, div2))
return true;
pos = [x + w, y];
if (isInside (pos, div2))
return true;
pos = [x + w, y + h];
if (isInside (pos, div2))
return true;
pos = [x, y + h];
if (isInside (pos, div2))
return true;
return false;
}
Here's another implementation of collision detection close to what you asked for. The two main goals being:
move vertically more than horizontally (because boxes are wider than tall)
stay within a reasonable range from the origin
Here goes:
function yCollision($elem) {
var $result = null;
$('.label').each(function() {
var $candidate = $(this);
if (!$candidate.is($elem) &&
$candidate.position().top <= $elem.position().top + $elem.outerHeight() &&
$candidate.position().top + $candidate.outerHeight() >= $elem.position().top) {
$result = $candidate;
console.log("BUMP Y");
}
});
return $result;
}
function xCollision($elem) {
var $result = null;
$('.label').each(function() {
$candidate = $(this);
if (!$candidate.is($elem) &&
yCollision($elem) &&
yCollision($elem).is($candidate) &&
$candidate.position().left <= $elem.position().left + $elem.outerWidth() &&
$candidate.position().left + $candidate.outerWidth() >= $elem.position().left) {
$result = $candidate;
console.log("BUMP X");
}
});
return $result;
}
function fuzzyMoveY($elem, direction) {
var newTop = $elem.position().top + $elem.outerHeight() / 4 * direction;
// stay in the canvas - top border
newTop = (newTop < 0 ? 0 : newTop);
// stay in the canvas - bottom border
newTop = (newTop + $elem.outerHeight() > $("#labelzone").outerHeight() ? $("#labelzone").outerHeight() - $elem.outerHeight() : newTop);
// stay close to our origin
newTop = (Math.abs(newTop - $elem.attr("data-origin-top")) > $elem.outerHeight() ? $elem.attr("data-origin-top") : newTop);
$elem.css({'top': newTop});
}
function fuzzyMoveX($elem, direction) {
var newLeft = $elem.position().left + $elem.outerWidth() / 4 * direction;
// stay in the canvas - left border
newLeft = (newLeft < 0 ? 0 : newLeft);
// stay in the canvas - right border
newLeft = (newLeft + $elem.outerWidth() > $("#labelzone").outerWidth() ? $("#labelzone").outerWidth() - $elem.outerWidth() : newLeft);
// stay close to our origin
newLeft = (Math.abs(newLeft - $elem.attr("data-origin-left")) > $elem.outerWidth() ? $elem.attr("data-origin-left") : newLeft);
$elem.css({'left': newLeft});
}
function bumpY($above, $below) {
if ($above.position().top > $below.position().top) {
$buff = $above;
$above = $below;
$below = $buff;
}
fuzzyMoveY($above, -1);
fuzzyMoveY($below, 1);
}
function bumpX($left, $right) {
if ($left.position().left > $right.position().left) {
$buff = $right;
$right = $left;
$left = $buff;
}
fuzzyMoveX($left, 1);
fuzzyMoveX($right, -1);
}
$('.label').each(function() {
$(this).attr('data-origin-left', $(this).position().left);
$(this).attr('data-origin-top', $(this).position().top);
});
var yShallPass = true;
var loopCount = 0;
while (yShallPass && loopCount < 10) {
yShallPass = false;
$('.label').each(function() {
var $this = $(this);
$collider = yCollision($this);
if ($collider) {
bumpY($this, $collider);
yShallPass = true;
}
});
loopCount++;
}
console.log("y loops", loopCount);
var xShallPass = true;
var loopCount = 0;
while (xShallPass && loopCount < 10) {
xShallPass = false;
$('.label').each(function() {
var $this = $(this);
$collider = xCollision($this);
if ($collider) {
bumpX($this, $collider);
xShallPass = true;
}
});
loopCount++;
}
console.log("x loops", loopCount);
This is not production code obviously but please report back if it helps.
can anyone figure out why this JavaScript won't work? It correctly generates the document.write output, but when you try to drag it it starts complaining about top and left not being set. any idea whats wrong?
abilitynames=new Array('Heal','Slash','Stab','Poison Slash','Knockout','','','','Tornado','','','','','','','','Icespike','','','','','','','','Bolt','Jumping Bolt','','','','','','','Roast','Burn','','','','','','','Earthquake','Rockwall','','','','','','','Kill','Deflect','Anti-Heal','','','','','','Backslash','Darkwall','Steal','Take','','','','');
abilitytypes=new Array(3,1,1,1,1,2,2,2,1,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,1,1,2,2,2,2,2,2,1,1,2,2,2,2,2,2,1,2,2,2,2,2,2,2,1,2,3,2,2,2,2,2,1,2,3,3,2,2,2,2);
abilitycolors=new Array('#00FF00','#FFFFFF','#0000FF','#FFFF00','#FF0000','#AD6C00','#AD00FF','#000000');
for(i=0;i<64;i++){
document.write("<div onmousedown='dragging=this.id;this.style.position=\"fixed\";this.style.zIndex=1000;this.style.left=event.clientX-75;this.style.top=event.clientY-15;' onmouseout='if(dragging===this.id){this.style.left=event.clientX-75;this.style.top=event.clientY-15;}' onmousemove='if(dragging===this.id){this.style.left=event.clientX-75;this.style.top=event.clientY-15;}' onmouseup='dragging=false;if(event.clientX<450||event.clientY>"+(180+(abilitytypes[i]*90))+"||event.clientY<"+(100+((abilitytypes[i]-1)*(90*abilitytypes[i])/((4%abilitytypes[i])+1)))+"){this.style.position=\"relative\";this.style.top=0;this.style.left=0;this.style.zIndex=0;}else{this.style.left=460;this.style.top=(Math.round((event.clientY-30)/45)*45)+15;if(abilitys[Math.round((event.clientY-120)/45)]!=\"\"&&abilitys[Math.round((event.clientY-120)/45)]!=this.id){document.getElementById(abilitys[Math.round((event.clientY-120)/45)]).style.position=\"relative\";document.getElementById(abilitys[Math.round((event.clientY-120)/45)]).style.left=0;document.getElementById(abilitys[Math.round((event.clientY-120)/45)]).style.top=0;}abilitys[Math.round((event.clientY-120)/45)]=this.id;alert(abilitys);}' id='"+i+"' class='abilityblock"+abilitytypes[i]+"'><div class='abilityicon' style='background-position:"+(Math.floor(i/8)*-20)+"px "+((i%8)*-20)+"px;'></div><div class='abilityname' style='color:"+abilitycolors[Math.floor(i/8)]+";'>"+abilitynames[i]+"</div></div>");
}
I've probably broken the script just TRYING to clean up this unholy mess and simplifying things a little, but at least it seems to be a bit more readable now (not including the array definitions):
<script type="text/javascript">
var dragging;
function mouseDown(el) {
dragging = el.id;
el.style.position = "fixed";
el.style.zIndex = 1000;
el.style.left = event.clientX - 75;
el.style.top = event.clientY-15;
}
function mouseOut(el) {
if (dragging === el.id) {
el.style.left = event.clientX - 75;
el.style.top = event.clientY - 15;
}
}
function mouseMove(el) {
if (dragging === el.id) {
el.style.left = event.clientX - 75;
el.style.top = event.clientY - 15;
}
}
function mouseUp(el, i) {
dragging = false;
if ( (event.clientX < 450) ||
(event.clientY > (180 + (abilitytypes[i] * 90)) ) ||
(event.clientY < (100 + (abilitytypes[i] - 1) * (90 * abilitytypes[i]) / ((4 % abilitytypes[i]) + 1)))) {
el.style.position = "relative";
el.style.top = 0;
el.style.left = 0;
el.style.zIndex = 0;
} else {
el.style.left = 460;
el.style.top = (Math.round((event.clientY - 30) / 45) * 45) + 15;
if ((abilitys[Math.round((event.clientY - 120) / 45)] != "") && (abilitys[Math.round((event.clientY - 120) / 45)] != el.id)) {
var subel = document.getElementById(abilitys[Math.round((event.clientY-120)/45)]);
subel.style.position="relative";
subel.style.left=0;
subel.style.top=0;
}
abilitys[Math.round((event.clientY - 120) / 45)] = el.id;
alert(abilitys);
}
}
for(var i = 0; i < 64; i++){
document.write("
<div onmousedown='mouseDown(this);'
onmouseout='mouseOut(this);'
onmousemove='mouseMove(el);'
onmouseup='mouseUp(this, i);'
id='"+i+"'
class='abilityblock"+abilitytypes[i]+"'>
<div class='abilityicon' style='background-position:"+(Math.floor(i/8)*-20)+"px "+((i%8)*-20)+"px;'></div>
<div class='abilityname' style='color:"+abilitycolors[Math.floor(i/8)]+";'>"+abilitynames[i]+"</div>
</div>");
}
</script>
Phew. And after all that, I'm guessing your missing 'left' and 'top' parameters are because your dynamically computed element IDs are generating non-existent IDs in the mouseup handler.
My suggestion? Scrap this home-brew drag 'n drop stuff and use the functionality provided by Mootools or jQuery. Far less trouble, especially when having to deal with cross-browser differences.
Searching your code, you haven't defined an onmouseover event. You define onmousedown, onmouseout, onmousemove, and onmouseup. No onmouseover.