JavaScript Css Animation - javascript

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.

Related

HTML Element Height Keeps Incrementing by the Second

Purchased an off the shelf theme for my portfolio site (yes, yes...I'm too busy to design my own right now lol). Anyway I seem to be having an issue, and I have no idea when it started, where my html height keeps growing by 30-60px every 1-2sec.
I've tried viewing the console, however I can't find anything that's triggering this. I'm assuming that some funking Javascript is at play, and need help possibly tracking it down.
Anyone wanna lend a hand here...?
My Site
UPDATE: It seems to be caused by the smoothscroll.js file. Can any JS pro help me find the offending line of code?
// SmoothScroll v1.2.1
// Licensed under the terms of the MIT license.
// People involved
// - Balazs Galambosi (maintainer)
// - Patrick Brunner (original idea)
// - Michael Herf (Pulse Algorithm)
// - Justin Force (Resurect)
// Scroll Variables (tweakable)
var framerate = 150; // [Hz]
var animtime = 800; // [px]
var stepsize = 80; // [px]
// Pulse (less tweakable)
// ratio of "tail" to "acceleration"
var pulseAlgorithm = true;
var pulseScale = 8;
var pulseNormalize = 1;
// Acceleration
var acceleration = true;
var accelDelta = 10; // 20
var accelMax = 1; // 1
// Keyboard Settings
var keyboardsupport = true; // option
var disableKeyboard = false; // other reasons
var arrowscroll = 50; // [px]
// Excluded pages
var exclude = "";
var disabled = false;
// Other Variables
var frame = false;
var direction = { x: 0, y: 0 };
var initdone = false;
var fixedback = true;
var root = document.documentElement;
var activeElement;
var key = { left: 37, up: 38, right: 39, down: 40, spacebar: 32, pageup: 33, pagedown: 34, end: 35, home: 36 };
/**
* Sets up scrolls array, determines if frames are involved.
*/
function init() {
if (!document.body) return;
var body = document.body;
var html = document.documentElement;
var windowHeight = window.innerHeight;
var scrollHeight = body.scrollHeight;
// check compat mode for root element
root = (document.compatMode.indexOf('CSS') >= 0) ? html : body;
activeElement = body;
initdone = true;
// Checks if this script is running in a frame
if (top != self) {
frame = true;
}
/**
* This fixes a bug where the areas left and right to
* the content does not trigger the onmousewheel event
* on some pages. e.g.: html, body { height: 100% }
*/
else if (scrollHeight > windowHeight &&
(body.offsetHeight <= windowHeight ||
html.offsetHeight <= windowHeight)) {
// DOMChange (throttle): fix height
var pending = false;
var refresh = function() {
if (!pending && html.scrollHeight != document.height) {
pending = true; // add a new pending action
setTimeout(function(){
html.style.height = document.height + 'px';
pending = false;
}, 500); // act rarely to stay fast
}
};
html.style.height = '';
setTimeout(refresh, 10);
addEvent("DOMNodeInserted", refresh);
addEvent("DOMNodeRemoved", refresh);
// clearfix
if (root.offsetHeight <= windowHeight) {
var underlay = document.createElement("div");
underlay.style.clear = "both";
body.appendChild(underlay);
}
}
// gmail performance fix
if (document.URL.indexOf("mail.google.com") > -1) {
var s = document.createElement("style");
s.innerHTML = ".iu { visibility: hidden }";
(document.getElementsByTagName("head")[0] || html).appendChild(s);
}
// disable fixed background
if (!fixedback && !disabled) {
body.style.backgroundAttachment = "scroll";
html.style.backgroundAttachment = "scroll";
}
}
/************************************************
* SCROLLING
************************************************/
var que = [];
var pending = false;
var lastScroll = +new Date;
/**
* Pushes scroll actions to the scrolling queue.
*/
function scrollArray(elem, left, top, delay) {
delay || (delay = 1000);
directionCheck(left, top);
if (acceleration) {
var now = +new Date;
var elapsed = now - lastScroll;
if (elapsed < accelDelta) {
var factor = (1 + (30 / elapsed)) / 2;
if (factor > 1) {
factor = Math.min(factor, accelMax);
left *= factor;
top *= factor;
}
}
lastScroll = +new Date;
}
// push a scroll command
que.push({
x: left,
y: top,
lastX: (left < 0) ? 0.99 : -0.99,
lastY: (top < 0) ? 0.99 : -0.99,
start: +new Date
});
// don't act if there's a pending queue
if (pending) {
return;
}
var scrollWindow = (elem === document.body);
var step = function() {
var now = +new Date;
var scrollX = 0;
var scrollY = 0;
for (var i = 0; i < que.length; i++) {
var item = que[i];
var elapsed = now - item.start;
var finished = (elapsed >= animtime);
// scroll position: [0, 1]
var position = (finished) ? 1 : elapsed / animtime;
// easing [optional]
if (pulseAlgorithm) {
position = pulse(position);
}
// only need the difference
var x = (item.x * position - item.lastX) >> 0;
var y = (item.y * position - item.lastY) >> 0;
// add this to the total scrolling
scrollX += x;
scrollY += y;
// update last values
item.lastX += x;
item.lastY += y;
// delete and step back if it's over
if (finished) {
que.splice(i, 1); i--;
}
}
// scroll left and top
if (scrollWindow) {
window.scrollBy(scrollX, scrollY)
}
else {
if (scrollX) elem.scrollLeft += scrollX;
if (scrollY) elem.scrollTop += scrollY;
}
// clean up if there's nothing left to do
if (!left && !top) {
que = [];
}
if (que.length) {
requestFrame(step, elem, (delay / framerate + 1));
} else {
pending = false;
}
}
// start a new queue of actions
requestFrame(step, elem, 0);
pending = true;
}
/***********************************************
* EVENTS
***********************************************/
/**
* Mouse wheel handler.
* #param {Object} event
*/
function wheel(event) {
if (!initdone) {
init();
}
var target = event.target;
var overflowing = overflowingAncestor(target);
// use default if there's no overflowing
// element or default action is prevented
if (!overflowing || event.defaultPrevented ||
isNodeName(activeElement, "embed") ||
(isNodeName(target, "embed") && /\.pdf/i.test(target.src))) {
return true;
}
var deltaX = event.wheelDeltaX || 0;
var deltaY = event.wheelDeltaY || 0;
// use wheelDelta if deltaX/Y is not available
if (!deltaX && !deltaY) {
deltaY = event.wheelDelta || 0;
}
// scale by step size
// delta is 120 most of the time
// synaptics seems to send 1 sometimes
if (Math.abs(deltaX) > 1.2) {
deltaX *= stepsize / 120;
}
if (Math.abs(deltaY) > 1.2) {
deltaY *= stepsize / 120;
}
scrollArray(overflowing, -deltaX, -deltaY);
event.preventDefault();
}
/**
* Keydown event handler.
* #param {Object} event
*/
function keydown(event) {
var target = event.target;
var modifier = event.ctrlKey || event.altKey || event.metaKey ||
(event.shiftKey && event.keyCode !== key.spacebar);
// do nothing if user is editing text
// or using a modifier key (except shift)
// or in a dropdown
if ( /input|textarea|select|embed/i.test(target.nodeName) ||
target.isContentEditable ||
event.defaultPrevented ||
modifier ) {
return true;
}
// spacebar should trigger button press
if (isNodeName(target, "button") &&
event.keyCode === key.spacebar) {
return true;
}
var shift, x = 0, y = 0;
var elem = overflowingAncestor(activeElement);
var clientHeight = elem.clientHeight;
if (elem == document.body) {
clientHeight = window.innerHeight;
}
switch (event.keyCode) {
case key.up:
y = -arrowscroll;
break;
case key.down:
y = arrowscroll;
break;
case key.spacebar: // (+ shift)
shift = event.shiftKey ? 1 : -1;
y = -shift * clientHeight * 0.9;
break;
case key.pageup:
y = -clientHeight * 0.9;
break;
case key.pagedown:
y = clientHeight * 0.9;
break;
case key.home:
y = -elem.scrollTop;
break;
case key.end:
var damt = elem.scrollHeight - elem.scrollTop - clientHeight;
y = (damt > 0) ? damt+10 : 0;
break;
case key.left:
x = -arrowscroll;
break;
case key.right:
x = arrowscroll;
break;
default:
return true; // a key we don't care about
}
scrollArray(elem, x, y);
event.preventDefault();
}
/**
* Mousedown event only for updating activeElement
*/
function mousedown(event) {
activeElement = event.target;
}
/***********************************************
* OVERFLOW
***********************************************/
var cache = {}; // cleared out every once in while
setInterval(function(){ cache = {}; }, 10 * 1000);
var uniqueID = (function() {
var i = 0;
return function (el) {
return el.uniqueID || (el.uniqueID = i++);
};
})();
function setCache(elems, overflowing) {
for (var i = elems.length; i--;)
cache[uniqueID(elems[i])] = overflowing;
return overflowing;
}
function overflowingAncestor(el) {
var elems = [];
var rootScrollHeight = root.scrollHeight;
do {
var cached = cache[uniqueID(el)];
if (cached) {
return setCache(elems, cached);
}
elems.push(el);
if (rootScrollHeight === el.scrollHeight) {
if (!frame || root.clientHeight + 10 < rootScrollHeight) {
return setCache(elems, document.body); // scrolling root in WebKit
}
} else if (el.clientHeight + 10 < el.scrollHeight) {
overflow = getComputedStyle(el, "").getPropertyValue("overflow-y");
if (overflow === "scroll" || overflow === "auto") {
return setCache(elems, el);
}
}
} while (el = el.parentNode);
}
/***********************************************
* HELPERS
***********************************************/
function addEvent(type, fn, bubble) {
window.addEventListener(type, fn, (bubble||false));
}
function removeEvent(type, fn, bubble) {
window.removeEventListener(type, fn, (bubble||false));
}
function isNodeName(el, tag) {
return (el.nodeName||"").toLowerCase() === tag.toLowerCase();
}
function directionCheck(x, y) {
x = (x > 0) ? 1 : -1;
y = (y > 0) ? 1 : -1;
if (direction.x !== x || direction.y !== y) {
direction.x = x;
direction.y = y;
que = [];
lastScroll = 0;
}
}
var requestFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
function(callback, element, delay){
window.setTimeout(callback, delay || (1000/60));
};
})();
/***********************************************
* PULSE
***********************************************/
/**
* Viscous fluid with a pulse for part and decay for the rest.
* - Applies a fixed force over an interval (a damped acceleration), and
* - Lets the exponential bleed away the velocity over a longer interval
* - Michael Herf, http://stereopsis.com/stopping/
*/
function pulse_(x) {
var val, start, expx;
// test
x = x * pulseScale;
if (x < 1) { // acceleartion
val = x - (1 - Math.exp(-x));
} else { // tail
// the previous animation ended here:
start = Math.exp(-1);
// simple viscous drag
x -= 1;
expx = 1 - Math.exp(-x);
val = start + (expx * (1 - start));
}
return val * pulseNormalize;
}
function pulse(x) {
if (x >= 1) return 1;
if (x <= 0) return 0;
if (pulseNormalize == 1) {
pulseNormalize /= pulse_(1);
}
return pulse_(x);
}
addEvent("mousedown", mousedown);
addEvent("mousewheel", wheel);
addEvent("load", init);
Not sure what's going on here, but the issue with smoothscroll.js still stands. Disabling it seems to have no effect on the rest of my site though so I'm moving forward.

Move absolute positioned divs off eachoter

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.

Want the script corrected not to change the size of images

I want this script not to change the sizes of the images but the same sizes anywhere they are during sliding. The issue is somewhere in this code but i don't know which one that is changing the size. I want an even size
/**
* Given the item and position, this function will calculate the new data
* for the item. One the calculations are done, it will store that data in
* the items data object
*/
function performCalculations($item, newPosition) {
var newDistanceFromCenter = Math.abs(newPosition);
// Distance to the center
if (newDistanceFromCenter < options.flankingItems + 1) {
var calculations = data.calculations[newDistanceFromCenter];
} else {
var calculations = data.calculations[options.flankingItems + 1];
}
var distanceFactor = Math.pow(options.sizeMultiplier, newDistanceFromCenter)
var newWidth = distanceFactor * $item.data('original_width');
var newHeight = distanceFactor * $item.data('original_height');
var widthDifference = Math.abs($item.width() - newWidth);
var heightDifference = Math.abs($item.height() - newHeight);
var newOffset = calculations.offset
var newDistance = calculations.distance;
if (newPosition < 0) {
newDistance *= -1;
}
if (options.orientation == 'horizontal') {
var center = data.containerWidth / 2;
var newLeft = center + newDistance - (newWidth / 2);
var newTop = options.horizon - newOffset - (newHeight / 2);
} else {
var center = data.containerHeight / 2;
var newLeft = options.horizon - newOffset - (newWidth / 2);
var newTop = center + newDistance - (newHeight / 2);
}
var newOpacity;
if (newPosition === 0) {
newOpacity = 1;
} else {
newOpacity = calculations.opacity;
}
// Depth will be reverse distance from center
var newDepth = options.flankingItems + 2 - newDistanceFromCenter;
$item.data('width',newWidth);
$item.data('height',newHeight);
$item.data('top',newTop);
$item.data('left',newLeft);
$item.data('oldPosition',$item.data('currentPosition'));
$item.data('depth',newDepth);
$item.data('opacity',newOpacity);
}
function moveItem($item, newPosition) {
// Only want to physically move the item if it is within the boundaries
// or in the first position just outside either boundary
if (Math.abs(newPosition) <= options.flankingItems + 1) {
performCalculations($item, newPosition);
data.itemsAnimating++;
$item
.css('z-index',$item.data().depth)
// Animate the items to their new position values
.animate({
left: $item.data().left,
width: $item.data().width,
height: $item.data().height,
top: $item.data().top,
opacity: $item.data().opacity
}, data.currentSpeed, options.animationEasing, function () {
// Animation for the item has completed, call method
itemAnimationComplete($item, newPosition);
});
} else {
$item.data('currentPosition', newPosition)
// Move the item to the 'hidden' position if hasn't been moved yet
// This is for the intitial setup
if ($item.data('oldPosition') === 0) {
$item.css({
'left': $item.data().left,
'width': $item.data().width,
'height': $item.data().height,
'top': $item.data().top,
'opacity': $item.data().opacity,
'z-index': $item.data().depth
});
}
}
}

jQuery draggable with rotation - 'jerking' when dragging when rotated [duplicate]

As an experiment, I created a few div's and rotated them using CSS3.
.items {
position: absolute;
cursor: pointer;
background: #FFC400;
-moz-box-shadow: 0px 0px 2px #E39900;
-webkit-box-shadow: 1px 1px 2px #E39900;
box-shadow: 0px 0px 2px #E39900;
-moz-border-radius: 2px;
-webkit-border-radius: 2px;
border-radius: 2px;
}
I then randomly styled them and made them draggable via jQuery.
$('.items').each(function() {
$(this).css({
top: (80 * Math.random()) + '%',
left: (80 * Math.random()) + '%',
width: (100 + 200 * Math.random()) + 'px',
height: (10 + 10 * Math.random()) + 'px',
'-moz-transform': 'rotate(' + (180 * Math.random()) + 'deg)',
'-o-transform': 'rotate(' + (180 * Math.random()) + 'deg)',
'-webkit-transform': 'rotate(' + (180 * Math.random()) + 'deg)',
});
});
$('.items').draggable();
The dragging works, but I am noticing a sudden jump while dragging the div's only in webkit browsers, while everything is fine in Firefox.
If I remove the position: absolute style, the 'jumping' is even worse. I thought there was maybe a difference in the transform origin between webkit and gecko, but they are both at the centre of the element by default.
I have searched around already, but only came up with results about scrollbars or sortable lists.
Here is a working demo of my problem. Try to view it in both Safari/Chrome and Firefox. http://jsbin.com/ucehu/
Is this a bug within webkit or how the browsers render webkit?
I draw a image to indicate the offset after rotate on different browsers as #David Wick's answer.
Here's the code to fix if you don't want patch or modify jquery.ui.draggable.js
$(document).ready(function () {
var recoupLeft, recoupTop;
$('#box').draggable({
start: function (event, ui) {
var left = parseInt($(this).css('left'),10);
left = isNaN(left) ? 0 : left;
var top = parseInt($(this).css('top'),10);
top = isNaN(top) ? 0 : top;
recoupLeft = left - ui.position.left;
recoupTop = top - ui.position.top;
},
drag: function (event, ui) {
ui.position.left += recoupLeft;
ui.position.top += recoupTop;
}
});
});
or you can see the demo
This is a result of draggable's reliance on the jquery offset() function and offset()'s use of the native js function getBoundingClientRect(). Ultimately this is an issue with the jquery core not compensating for the inconsistencies associated with getBoundingClientRect(). Firefox's version of getBoundingClientRect() ignores the css3 transforms (rotation) whereas chrome/safari (webkit) don't.
here is an illustration of the issue.
A hacky workaround:
replace following in jquery.ui.draggable.js
//The element's absolute position on the page minus margins
this.offset = this.positionAbs = this.element.offset();
with
//The element's absolute position on the page minus margins
this.offset = this.positionAbs = { top: this.element[0].offsetTop,
left: this.element[0].offsetLeft };
and finally a monkeypatched version of your jsbin.
David Wick is right about the general direction above, but computing the right coordinates is way more involved than that. Here's a more accurate monkey patch, based on MIT licensed Firebug code, which should work in far more situations where you have a complex DOM:
Instead replace:
//The element's absolute position on the page minus margins
this.offset = this.positionAbs = this.element.offset();
with the less hacky (be sure to get the whole thing; you'll need to scroll):
//The element's absolute position on the page minus margins
this.offset = this.positionAbs = getViewOffset(this.element[0]);
function getViewOffset(node) {
var x = 0, y = 0, win = node.ownerDocument.defaultView || window;
if (node) addOffset(node);
return { left: x, top: y };
function getStyle(node) {
return node.currentStyle || // IE
win.getComputedStyle(node, '');
}
function addOffset(node) {
var p = node.offsetParent, style, X, Y;
x += parseInt(node.offsetLeft, 10) || 0;
y += parseInt(node.offsetTop, 10) || 0;
if (p) {
x -= parseInt(p.scrollLeft, 10) || 0;
y -= parseInt(p.scrollTop, 10) || 0;
if (p.nodeType == 1) {
var parentStyle = getStyle(p)
, localName = p.localName
, parent = node.parentNode;
if (parentStyle.position != 'static') {
x += parseInt(parentStyle.borderLeftWidth, 10) || 0;
y += parseInt(parentStyle.borderTopWidth, 10) || 0;
if (localName == 'TABLE') {
x += parseInt(parentStyle.paddingLeft, 10) || 0;
y += parseInt(parentStyle.paddingTop, 10) || 0;
}
else if (localName == 'BODY') {
style = getStyle(node);
x += parseInt(style.marginLeft, 10) || 0;
y += parseInt(style.marginTop, 10) || 0;
}
}
else if (localName == 'BODY') {
x += parseInt(parentStyle.borderLeftWidth, 10) || 0;
y += parseInt(parentStyle.borderTopWidth, 10) || 0;
}
while (p != parent) {
x -= parseInt(parent.scrollLeft, 10) || 0;
y -= parseInt(parent.scrollTop, 10) || 0;
parent = parent.parentNode;
}
addOffset(p);
}
}
else {
if (node.localName == 'BODY') {
style = getStyle(node);
x += parseInt(style.borderLeftWidth, 10) || 0;
y += parseInt(style.borderTopWidth, 10) || 0;
var htmlStyle = getStyle(node.parentNode);
x -= parseInt(htmlStyle.paddingLeft, 10) || 0;
y -= parseInt(htmlStyle.paddingTop, 10) || 0;
}
if ((X = node.scrollLeft)) x += parseInt(X, 10) || 0;
if ((Y = node.scrollTop)) y += parseInt(Y, 10) || 0;
}
}
}
It's a shame the DOM doesn't expose these calculations natively.
#ecmanaut: Great solution. Thanks for your efforts. To assist others I turned your solution into a monkey-patch. Copy below code to a file. Include the file after loading jquery-ui.js as follows:
<script src="javascripts/jquery/jquery.js"></script>
<script src="javascripts/jquery/jquery-ui.js"></script>
<!-- the file containing the monkey-patch to draggable -->
<script src="javascripts/jquery/patch_draggable.js"></script>
Here's the code to copy/paste into patch_draggable.js:
function monkeyPatch_mouseStart() {
// don't really need this, but in case I did, I could store it and chain
var oldFn = $.ui.draggable.prototype._mouseStart ;
$.ui.draggable.prototype._mouseStart = function(event) {
var o = this.options;
function getViewOffset(node) {
var x = 0, y = 0, win = node.ownerDocument.defaultView || window;
if (node) addOffset(node);
return { left: x, top: y };
function getStyle(node) {
return node.currentStyle || // IE
win.getComputedStyle(node, '');
}
function addOffset(node) {
var p = node.offsetParent, style, X, Y;
x += parseInt(node.offsetLeft, 10) || 0;
y += parseInt(node.offsetTop, 10) || 0;
if (p) {
x -= parseInt(p.scrollLeft, 10) || 0;
y -= parseInt(p.scrollTop, 10) || 0;
if (p.nodeType == 1) {
var parentStyle = getStyle(p)
, localName = p.localName
, parent = node.parentNode;
if (parentStyle.position != 'static') {
x += parseInt(parentStyle.borderLeftWidth, 10) || 0;
y += parseInt(parentStyle.borderTopWidth, 10) || 0;
if (localName == 'TABLE') {
x += parseInt(parentStyle.paddingLeft, 10) || 0;
y += parseInt(parentStyle.paddingTop, 10) || 0;
}
else if (localName == 'BODY') {
style = getStyle(node);
x += parseInt(style.marginLeft, 10) || 0;
y += parseInt(style.marginTop, 10) || 0;
}
}
else if (localName == 'BODY') {
x += parseInt(parentStyle.borderLeftWidth, 10) || 0;
y += parseInt(parentStyle.borderTopWidth, 10) || 0;
}
while (p != parent) {
x -= parseInt(parent.scrollLeft, 10) || 0;
y -= parseInt(parent.scrollTop, 10) || 0;
parent = parent.parentNode;
}
addOffset(p);
}
}
else {
if (node.localName == 'BODY') {
style = getStyle(node);
x += parseInt(style.borderLeftWidth, 10) || 0;
y += parseInt(style.borderTopWidth, 10) || 0;
var htmlStyle = getStyle(node.parentNode);
x -= parseInt(htmlStyle.paddingLeft, 10) || 0;
y -= parseInt(htmlStyle.paddingTop, 10) || 0;
}
if ((X = node.scrollLeft)) x += parseInt(X, 10) || 0;
if ((Y = node.scrollTop)) y += parseInt(Y, 10) || 0;
}
}
}
//Create and append the visible helper
this.helper = this._createHelper(event);
//Cache the helper size
this._cacheHelperProportions();
//If ddmanager is used for droppables, set the global draggable
if($.ui.ddmanager)
$.ui.ddmanager.current = this;
/*
* - Position generation -
* This block generates everything position related - it's the core of draggables.
*/
//Cache the margins of the original element
this._cacheMargins();
//Store the helper's css position
this.cssPosition = this.helper.css("position");
this.scrollParent = this.helper.scrollParent();
//The element's absolute position on the page minus margins
this.offset = this.positionAbs = getViewOffset(this.element[0]);
this.offset = {
top: this.offset.top - this.margins.top,
left: this.offset.left - this.margins.left
};
$.extend(this.offset, {
click: { //Where the click happened, relative to the element
left: event.pageX - this.offset.left,
top: event.pageY - this.offset.top
},
parent: this._getParentOffset(),
relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
});
//Generate the original position
this.originalPosition = this.position = this._generatePosition(event);
this.originalPageX = event.pageX;
this.originalPageY = event.pageY;
//Adjust the mouse offset relative to the helper if 'cursorAt' is supplied
(o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
//Set a containment if given in the options
if(o.containment)
this._setContainment();
//Trigger event + callbacks
if(this._trigger("start", event) === false) {
this._clear();
return false;
}
//Recache the helper size
this._cacheHelperProportions();
//Prepare the droppable offsets
if ($.ui.ddmanager && !o.dropBehaviour)
$.ui.ddmanager.prepareOffsets(this, event);
this.helper.addClass("ui-draggable-dragging");
//JWL: Hier vindt de jump plaats
this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position
//If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003)
if ( $.ui.ddmanager ) $.ui.ddmanager.dragStart(this, event);
return true;
};
}
monkeyPatch_mouseStart();
I prefer this workaround as it preserves the original handler
It removes the transform then restores it
$(document).ready(function(){
// backup original handler
var _mouseStart = $.ui.draggable.prototype._mouseStart;
$.ui.draggable.prototype._mouseStart = function(event) {
//remove the transform
var transform = this.element.css('transform');
this.element.css('transform', 'none');
// call original handler
var result = _mouseStart.call(this, event);
//restore the transform
this.element.css('transform', transform);
return result;
};
});
demo (started from #Liao San-Kai jsbin)
the answer of David Wick was very helpful... thanks...
here i coded the same workaround for the resizeable, because it has the same problem:
search for the following in jquery.ui.resizable.js
var o = this.options, iniPos = this.element.position(), el = this.element;
and replace with:
var o = this.options, iniPos = {top:this.element[0].offsetTop,left:this.element[0].offsetLeft}, el = this.element;
I used a lot of the solutions to get dragging working correctly. BUT, it still reacted wrong to a dropzone (like it wasn't rotated). The Solution really is to use a parent container that is positioned relative.
This saved me soooo much time.
<div id="drawarea">
<div class="rect-container h">
<div class="rect"></div>
</div>
</div>
.rect-container {
position:relative;
}
Full Solution here (it's not from me):
http://jsfiddle.net/Sp6qa/2/
Also I researched a lot. And its just like this, jQuery doesn't have any plans to change that current behavior in the future. All submitted tickets about that topic were closed. So just start out with having parentcontainers that are positioned relative. It works like a charm and should be futureproof.
You have to set the parent container of the draggable element to "position: relative".

This javascript fails on mouseover

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.

Categories