This is a follow up from my previous question.
I have a progressbar.js circle that animates on scroll. If there is just one circle it works as expected.
Now I want to create many of these animated circles by looping through an object with different key-values pairs.
For example:
var divsValues = {
'total-score-circle': 0.75,
'general-score-circle': 0.80,
'speed-score-circle': 0.85,
'privacy-score-circle': 0.90,
};
For each key-value pair, the key is a div ID and the value is number that tells the animation how far to go.
Below is the code where I try to implement my loop, but the problem is that only the last circle is animated on scroll. All the circles appear in their "pre-animation" state, but only the last circle actually becomes animated when you scroll to the bottom.
I need each circle to animate once it is in the viewport.
//Loop through my divs and create animated circle for each one
function makeCircles() {
var divsValues = {
'total-score-circle': 0.75,
'general-score-circle': 0.80,
'speed-score-circle': 0.85,
'privacy-score-circle': 0.90,
};
for (var i in divsValues) {
if (divsValues.hasOwnProperty(i)) {
bgCircles(i, divsValues[i]);
}
}
}
makeCircles();
// Check if element is scrolled into view
function isScrolledIntoView(elem) {
var docViewTop = jQuery(window).scrollTop();
var docViewBottom = docViewTop + jQuery(window).height();
var elemTop = jQuery(elem).offset().top;
var elemBottom = elemTop + jQuery(elem).height();
return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));
}
//Circle design and animation
function bgCircles(divid, countvalue) {
// Design the circle using progressbar.js
bar = new ProgressBar.Circle(document.getElementById(divid), {
color: '#ddd',
// This has to be the same size as the maximum width to
// prevent clipping
strokeWidth: 4,
trailWidth: 4,
easing: 'easeInOut',
duration: 1400,
text: {
autoStyleContainer: false
},
from: {
color: '#ddd',
width: 4
},
to: {
color: '#888',
width: 4
},
// Set default step function for all animate calls
step: function(state, circle) {
circle.path.setAttribute('stroke', state.color);
circle.path.setAttribute('stroke-width', state.width);
var value = Math.round(circle.value() * 100);
if (value === 0) {
circle.setText('');
} else {
circle.setText(value + '%');
}
}
});
bar.text.style.fontFamily = '"Montserrat", sans-serif';
bar.text.style.fontSize = '1.7rem';
bar.trail.setAttribute('stroke-linecap', 'round');
bar.path.setAttribute('stroke-linecap', 'round');
//Animate the circle when scrolled into view
window.onscroll = function() {
if (isScrolledIntoView(jQuery('#' + divid))) bar.animate(countvalue);
else bar.animate(0); // or bar.set(0)
}
}
#total-score-circle,
#general-score-circle,
#speed-score-circle,
#privacy-score-circle {
margin: 0.8em auto;
width: 100px;
height: 100px;
position: relative;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/progressbar.js/1.0.1/progressbar.min.js"></script>
<div id="total-score-circle"></div>
<div id="general-score-circle"></div>
<div id="speed-score-circle"></div>
<div id="privacy-score-circle"></div>
While researching this problem I learned that JavaScript will only output the last value of a loop, which I thought could be the cause of my problem.
So I tried to replace the for loop with these solutions...
Solution 1: Same problem as before, only the last loop animates on scroll.
for (var i in divsValues) {
(function(){
var ii = i;
if (divsValues.hasOwnProperty(ii)) {
bgCircles(ii, divsValues[ii]);
}
})();
}
Solution 2: Again, same problem as before, only the last loop animates on scroll.
for (var i in divsValues) {
let ii = i;
if (divsValues.hasOwnProperty(ii)) {
bgCircles(ii, divsValues[ii]);
}
}
Solution 3: Again, same problem as before, only the last loop animates on scroll.
for (var i in divsValues) {
try{throw i}
catch(ii) {
if (divsValues.hasOwnProperty(ii)) {
bgCircles(ii, divsValues[ii]);
}
}
}
So now I'm thinking maybe the problem is not the loop, but something I can't see or figure out.
You were quite close.
Here are the fixes:
In the function bgCircles(...), use var to declare the bar in that function's scope:
var bar = new ProgressBar.Circle(document.getElementById(divid), {
When you set the animate scrolled into view checker event, you assign a new function over-and-over to window.onscroll. Since you are using jQuery, consider jQuery's .scroll event handler and use it like this:
$(window).scroll(function () {
if (isScrolledIntoView(jQuery('#' + divid))) bar.animate(countvalue);
else bar.animate(0); // or bar.set(0)
});
The solution in whole:
//Loop through my divs and create animated circle for each one
function makeCircles() {
var divsValues = {
'total-score-circle': 0.75,
'general-score-circle': 0.80,
'speed-score-circle': 0.85,
'privacy-score-circle': 0.90,
};
for (var i in divsValues) {
if (divsValues.hasOwnProperty(i)) {
bgCircles(i, divsValues[i]);
}
}
}
makeCircles();
// Check if element is scrolled into view
function isScrolledIntoView(elem) {
var docViewTop = jQuery(window).scrollTop();
var docViewBottom = docViewTop + jQuery(window).height();
var elemTop = jQuery(elem).offset().top;
var elemBottom = elemTop + jQuery(elem).height();
return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));
}
//Circle design and animation
function bgCircles(divid, countvalue) {
// Design the circle using progressbar.js
var bar = new ProgressBar.Circle(document.getElementById(divid), {
color: '#ddd',
// This has to be the same size as the maximum width to
// prevent clipping
strokeWidth: 4,
trailWidth: 4,
easing: 'easeInOut',
duration: 1400,
text: {
autoStyleContainer: false
},
from: {
color: '#ddd',
width: 4
},
to: {
color: '#888',
width: 4
},
// Set default step function for all animate calls
step: function(state, circle) {
circle.path.setAttribute('stroke', state.color);
circle.path.setAttribute('stroke-width', state.width);
var value = Math.round(circle.value() * 100);
if (value === 0) {
circle.setText('');
} else {
circle.setText(value + '%');
}
}
});
bar.text.style.fontFamily = '"Montserrat", sans-serif';
bar.text.style.fontSize = '1.7rem';
bar.trail.setAttribute('stroke-linecap', 'round');
bar.path.setAttribute('stroke-linecap', 'round');
//Animate the circle when scrolled into view
$(window).scroll(function () {
if (isScrolledIntoView(jQuery('#' + divid))) bar.animate(countvalue);
else bar.animate(0); // or bar.set(0)
});
}
#total-score-circle,
#general-score-circle,
#speed-score-circle,
#privacy-score-circle {
margin: 0.8em auto;
width: 100px;
height: 100px;
position: relative;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/progressbar.js/1.0.1/progressbar.min.js"></script>
<div id="total-score-circle"></div>
<div id="general-score-circle"></div>
<div id="speed-score-circle"></div>
<div id="privacy-score-circle"></div>
Note:
Since I didn't edit any of your circle animation/circle visibility checking functions, I assume you intended the current state of your animate-when-scrolled-and-in-view functionality the way, that it is right now. In this current state, your script does/has the following side-effects:
If you don't scroll the page, not at all, your circles won't start to animate, even, when they are visible. Solution: encapsulate the visibility checker lines to a separate function and run, when creating the circles.
If you scroll through a circle, its animation with the percent will going to go to its default state, which is 0%. Solution: change the visibility checker function, when the particular element is not visible because of overscroll, return that state as visible too. This way your circles will stay at 100%, even when you scroll over them.
Regarding performance and best practices:
When using jQuery, make sure to call jQuery(...) or its shorthand $(...) as few times as possible. Use variables to store elements, properties, and data.
It's better to separate longer/larger, monolithic functions into smaller functions with a more narrow, but also more clear scope of functionality.
When using event listeners, make sure to run as few of them as possible. Structure your HTML and your JavaScript code to have clear and performant ways to access and modify your crucial elements, properties, and data.
The loop you have will run so fast that the browser engine wont be able to render the changes, I would suggest either you use setInterval() method or continuous setTimeout() method which will add some delay to your code so that the browser can render the changes you are making.
For your special case I would suggest:
var i = 0;
var tobecleared = setInterval(timer,1000);
function timer(){
var p = get_ith_key_from_divsvalues(i);//implement this method
console.log(p);
bgCircles(p, divsValues[p]);
i++;
if(i == Object.keys(divsValues).length)
clearInterval(tobecleared);
}
function get_ith_key_from_divsvalues(i){
var j = -1;
for(var property in divsValues){
j++;
if(j==i)
return property;
}
}
Note : window.onscroll is being overwritten in each call that is why only the last circle responds.
There are two fixes you need to apply.
Currently bar is the global variable, so it's always the same, to fix it declare it with var.
Use window.addEventListener to attach scroll event to window, by setting handler with window.onscroll you constantly override event handler and usage of addEventListener allow you attach more than one event handler.
//Loop through my divs and create animated circle for each one
$( document ).ready(function(){
function makeCircles() {
var divsValues = {
'total-score-circle': 0.75,
'general-score-circle': 0.80,
'speed-score-circle': 0.85,
'privacy-score-circle': 0.90,
};
for (var i in divsValues) {
if (divsValues.hasOwnProperty(i)) {
bgCircles(i, divsValues[i]);
}
}
}
makeCircles();
// Check if element is scrolled into view
function isScrolledIntoView(elem) {
var docViewTop = jQuery(window).scrollTop();
var docViewBottom = docViewTop + jQuery(window).height();
var elemTop = jQuery(elem).offset().top;
var elemBottom = elemTop + jQuery(elem).height();
return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));
}
//Circle design and animation
function bgCircles(divid, countvalue) {
// Design the circle using progressbar.js
var bar = new ProgressBar.Circle(document.getElementById(divid), {
color: '#ddd',
// This has to be the same size as the maximum width to
// prevent clipping
strokeWidth: 4,
trailWidth: 4,
easing: 'easeInOut',
duration: 1400,
text: {
autoStyleContainer: false
},
from: {
color: '#ddd',
width: 4
},
to: {
color: '#888',
width: 4
},
// Set default step function for all animate calls
step: function(state, circle) {
circle.path.setAttribute('stroke', state.color);
circle.path.setAttribute('stroke-width', state.width);
var value = Math.round(circle.value() * 100);
if (value === 0) {
circle.setText('');
} else {
circle.setText(value + '%');
}
}
});
bar.text.style.fontFamily = '"Montserrat", sans-serif';
bar.text.style.fontSize = '1.7rem';
bar.trail.setAttribute('stroke-linecap', 'round');
bar.path.setAttribute('stroke-linecap', 'round');
//Animate the circle when scrolled into view
window.addEventListener('scroll', function() {
if (isScrolledIntoView(jQuery('#' + divid))) bar.animate(countvalue);
else bar.animate(0); // or bar.set(0)
})
}
})
#total-score-circle,
#general-score-circle,
#speed-score-circle,
#privacy-score-circle {
margin: 0.8em auto;
width: 100px;
height: 100px;
position: relative;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/progressbar.js/1.0.1/progressbar.min.js"></script>
<div id="total-score-circle"></div>
<div id="general-score-circle"></div>
<div id="speed-score-circle"></div>
<div id="privacy-score-circle"></div>
Related
I have a zooming feature in my fabricjs app. The that runs zoom is just very similar to this: http://fabricjs.com/fabric-intro-part-5
canvas.on('mouse:wheel', function(opt) {
var delta = opt.e.deltaY;
var zoom = canvas.getZoom();
zoom *= 0.999 ** delta;
if (zoom > 20) zoom = 20;
if (zoom < 0.01) zoom = 0.01;
// canvas.zoomToPoint({ x: opt.e.offsetX, y: opt.e.offsetY }, zoom); commented because I run canvas.zoomToPoint in main canvas update function, not just inside handler
this.zoom = zoom;
opt.e.preventDefault();
opt.e.stopPropagation();
});
Now I would like to program a smooth zooming on mouse wheel - so that it zooms like here: https://mudin.github.io/indoorjs but I completely don't know where to start.
I feel like I need to debounce somehow the handler for mouse wheel, because for now it happens whenever you wheel the mouse - is this the right direction? How to accomplish something like that?
Maybe this is usefull. There is an eventlistener for the wheel event and then I set the scale of the element and control the scaling in CSS.
var box = document.querySelector('#box');
box.scale = 1; // initial scale
box.addEventListener('wheel', e => {
e.preventDefault();
var deltaScale = (e.deltaY > 0) ? .1 : -.1;
e.target.scale = e.target.scale + deltaScale;
e.target.style.transform = `scale(${e.target.scale})`;
});
body {
display: flex;
justify-content: space-around;
align-items: center;
height: 100vh;
}
#box {
border: thin solid black;
width: 100px;
height: 100px;
transition: transform 1s ease-in-out;
}
<div id="box">content</div>
For anyone still wondering how to implement smooth zooming in Fabric.js
First you need to register a change event for the mouse wheel:
(keeping track of the values in a Mouse object)
canvas.on('mouse:wheel', (e) => {
Mouse.x = e.e.offsetX;
Mouse.y = e.e.offsetY;
Mouse.w += e.e.wheelDelta;
return false;
});
Then you need to call this function in your render loop when Mouse.w !== 0:
let targetZoomLevel = canvas.getZoom();
let isAnimationRunning = false;
let abort = false;
// Call this function in your render loop if Mouse.W != 0
function zoom() {
// mousewheel value changed: abort animation if it's running
abort = isAnimationRunning;
// get current zoom level
let curZoom = canvas.getZoom();
// Add mousewheel delta to target zoom level
targetZoomLevel += Mouse.w / 2000;
// Animate the zoom if smooth zooming is enabled
fabric.util.animate({
startValue: curZoom,
endValue: targetZoomLevel,
// Set this value to your liking
duration: 500,
easing: fabric.util.ease.easeOutQuad,
onChange: (newZoomValue) => {
isAnimationRunning = true;
// Zoom to mouse pointer location
canvas.zoomToPoint({
x: Mouse.x,
y: Mouse.y
}, newZoomValue);
// call canvas.renderAll here
},
onComplete: () => {
isAnimationRunning = false;
},
abort: () => {
// The animation aborts if this function returns true
let abortValue = abort;
if (abortValue == true) {
abort = false;
}
return abortValue;
}
});
}
It aborts the animation when the mousewheel value changes and adds the value to a zoom target
I'm making a 3D box with Zdog and I want to let the height of the box grow.
Here is a codepen: https://codepen.io/anon/pen/qzBMgp.
This is my code for the box:
let progressBox = new Zdog.Box({
addTo: progress,
width: 200,
height: boxHeight,
depth: 200,
stroke: 1,
})
This is the code I use to increase the height of the box. If the box is shorter than 400, the box will increase its height with 0.1.
function animate() {
if (boxHeight < 400) {
moveUp = 'up';
} else if (boxHeight > 400) {
moveUp = 'false';
}
boxHeight += moveUp == 'up' ? 0.1 : 0;
}
The problem is that the box stays at a height of 0 (the value I gave to boxHeight), but when I console.log(boxHeight) the boxHeight will grow.
First of all, let me point out that you cannot change the value of a constant . On your code, the boxHeight is declared as const.
Second, you will need to use Zdog's copy() method. Here is your code modified accordingly.
Zdog.Anchor.prototype.renderGraphSvg = function (svg) {
if (!svg) {
throw new Error('svg is ' + svg + '. ' +
'SVG required for render. Check .renderGraphSvg( svg ).');
}
this.flatGraph.forEach(function (item) {
item.render(svg, Zdog.SvgRenderer);
});
};
const TAU = Zdog.TAU;
const light = '#EAE2B7';
const yellow1 = '#FCBF49';
const orange1 = '#F77F00';
const red1 = '#d62828';
const purple1 = '#003049';
const white1 = '#ffffff';
const isSpinning = true;
var boxHeight = 0;
let progress = new Zdog.Illustration({
element: '.progress',
dragRotate: true,
translate: {
y: 25
},
rotate: {
x: -0.4, y: 0.75
}
});
let progressBox = new Zdog.Box({
addTo: progress,
width: 200,
depth: 200,
height: boxHeight,
stroke: 1,
color: purple1, // default face color
leftFace: yellow1,
rightFace: orange1,
topFace: red1,
bottomFace: light,
translate: {
x: 0,
y: 300
},
});
function animate() {
if (boxHeight <= 400) {
boxHeight++; // 1
progressBox = progressBox.copy({
height: boxHeight, // overwrite height
translate: {
y: progressBox.translate.y - 1 // overwrite vertical position to put box in place while height is growing.
}
});
}
progress.updateRenderGraph();
requestAnimationFrame(animate);
}
animate();
I forked your pen and updated it with the code above. See Zdog - progress box
Note that it seems to be expensive doing the copy() method on every animation frame. I am also new to this library and this is currently the fix I know of.
I want to create the effect similar to the old mouse trails where the div is delayed but follows the cursor.
I have come reasonably close by using set interval to trigger an animation to the coordinates of the cursor.
$("body").mousemove(function (e) {
if (enableHandler) {
handleMouseMove(e);
enableHandler = false;
}
});
timer = window.setInterval(function(){
enableHandler = true;
}, 250);
function handleMouseMove(e) {
var x = e.pageX,
y = e.pageY;
$("#cube").animate({
left: x,
top: y
}, 200);
}
JSFiddle
There are two problems that remain now:
The 'chasing' div is very jumpy (because of the required use of set interval)
If the mouse move stops before the animation is triggered, the div is left in place, away from the cursor.
I did it slightly differently. Instead of using setInterval (or even setTimeout) - I just made the animation take x amount of milliseconds to complete. The longer the animation, the less responsive the following div will seem to be.
The only problem I notice is that it gets backed up if the mouse is moved a lot.
$(document).ready(function () {
$("body").mousemove(function (e) {
handleMouseMove(e);
});
function handleMouseMove(event) {
var x = event.pageX;
var y = event.pageY;
$("#cube").animate({
left: x,
top: y
}, 1);
}
});
https://jsfiddle.net/jvmravoz/1/
Remove SetInterval and add a $("#cube").stop(); to stop the old animation based on old (x,y) so you can start a new "faster" one.
$(document).ready(function() {
$("body").mousemove(function (e) {
$("#cube").stop();
handleMouseMove(e);
});
function handleMouseMove(event) {
var x = event.pageX,
y = event.pageY;
$("#cube").animate({
left: x,
top: y
}, 50);
}
});
Working example
https://jsfiddle.net/jabnxgp7/
Super late to the game here but I didn't really like any of the options for adding a delay here since they follow the mouse's previous position instead of moving towards the mouse. So I heavily modified the code from Mike Willis to get this -
$(document).ready(function () {
$("body").mousemove(function (e) {
mouseMoveHandler(e);
});
var currentMousePos = { x: -1, y: -1 };
function mouseMoveHandler(event) {
currentMousePos.x = event.pageX;
currentMousePos.y = event.pageY;
}
mouseMover = setInterval(positionUpdate, 15);
function positionUpdate() {
var x_cursor = currentMousePos.x;
var y_cursor = currentMousePos.y;
var position = $("#cube").offset();
var x_box = position.left;
var y_box = position.top;
$("#cube").animate({
left: x_box+0.1*(x_cursor-x_box),
top: y_box+0.1*(y_cursor-y_box)
}, 1, "linear");
}
});
-----------------------------------------------------------------------
body { overflow:hidden; position:absolute; height:100%; width:100%; background:#efefef; }
#cube {
height:18px;
width:18px;
margin-top:-9px;
margin-left:-9px;
background:red;
position:absolute;
top:50%;
left:50%;
}
.circleBase {
border-radius: 50%;
}
.roundCursor {
width: 20px;
height: 20px;
background: red;
border: 0px solid #000;
}
https://jsfiddle.net/rbd1p2s7/3/
It saves the cursor position every time it moves and at a fixed interval, it updates the div position by a fraction of the difference between it and the latest cursor position. I also changed it to a circle since the circle looked nicer.
One concern here is that it triggers very often and could slow down a weak machine, reducing the update frequency makes the cursor jump more than I'd like, but maybe there's some middle ground between update frequency and jumpiness to be found, or using animation methods I'm not familiar with to automate the movement.
Here is a solution that might mimic the mouse-trail a bit more because it is only remembering the last 100 positions and discarding older ones which kind of sets the length of the mouse trail.
https://jsfiddle.net/acmvhgzm/6/
$(document).ready(function() {
var pos = new Array();
$("body").mousemove(function (e) {
handleMouseMove(e);
});
timer = window.setInterval(function() {
if (pos.length > 0) {
$('#cube').animate(pos.shift(),15);
}
}, 20);
function handleMouseMove(event) {
var x = event.pageX,
y = event.pageY;
if (pos.length = 100) {
pos.shift();
}
pos.push({'left':x, 'top':y});
}
});
Old mouse-trail feature used a list of several windows shaped like cursors which updated their positions with every frame. Basically, it had a list of "cursors" and every frame next "cursor" in list was being moved to current cursor position, achieving effect of having every fake cursor update its own position with a delay of fake cursors - 1 frames.
Smooth, on-demand delayed movement for a single object can be simulated using requestAnimationFrame, performance.now and Event.timeStamp. Idea is to hold mouse events in internal list and use them only after specific time passed after their creation.
function DelayLine(delay, action){
capacity = Math.round(delay / 1000 * 200);
this.ring = new Array(capacity);
this.delay = delay;
this.action = action;
this._s = 0;
this._e = 0;
this._raf = null;
this._af = this._animationFrame.bind(this);
this._capacity = capacity;
}
DelayLine.prototype.put = function(value){
this.ring[this._e++] = value;
if (this._e >= this._capacity) this._e = 0;
if (this._e == this._s) this._get();
cancelAnimationFrame(this._raf);
this._raf = requestAnimationFrame(this._af);
}
DelayLine.prototype._get = function(){
var value = this.ring[this._s++];
if (this._s == this._capacity) this._s = 0;
return value;
}
DelayLine.prototype._peek = function(){
return this.ring[this._s];
}
DelayLine.prototype._animationFrame = function(){
if (this._length > 0){
if (performance.now() - this._peek().timeStamp > this.delay)
this.action(this._get());
this._raf = requestAnimationFrame(this._af);
}
}
Object.defineProperty(DelayLine.prototype, "_length", {
get: function() {
var size = this._e - this._s;
return size >= 0 ? size : size + this._capacity;
}
});
var delayLine = new DelayLine(100, function(e){
pointer.style.left = e.x - pointer.offsetWidth/2 + "px";
pointer.style.top = e.y - pointer.offsetHeight/2 + "px";
});
document.addEventListener("mousemove", function(e){
delayLine.put(e);
}, false);
https://jsfiddle.net/os8r7c20/2/
Try removing setInterval , using .css() , css transition
$(document).ready(function () {
var cube = $("#cube");
$("body").mousemove(function (e) {
handleMouseMove(e);
});
function handleMouseMove(event) {
var x = event.pageX,
y = event.pageY;
cube.css({
left: x + cube.width() / 2 + "px",
top: y + cube.height() / 2 + "px"
}).parents("body").mousemove()
}
});
body {
overflow:hidden;
position:absolute;
height:100%;
width:100%;
background:#efefef;
}
#cube {
height:50px;
width:50px;
margin-top:-25px;
margin-left:-25px;
background:red;
position:absolute;
top:50%;
left:50%;
transition:all 1.5s ease-in-out;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="container">
<div id="cube"></div>
</div>
I want to expand the paper in case of the user is moving an element to the bottom or right border:
var paper = new joint.dia.Paper({
el: $('#canvas'),
width: 801,
height: 496,
model: graph
});
rect.on('change:position', function(evt, pos) {
if (pos.y > 400) { height = pos.y + 96; }
else { height = 496; }
if (pos.x > 680) { width = pos.x + 121; }
else { width = 801; }
paper.setDimensions(width, height);
});
This is working for just a single element. But if there is already another element, this doesn't work.
So I need to find the element(s) with the highest x or y value. That means I search for the element which is the most right or bottom of the paper. Then I would put this into another if-clause.
I thought of using paper.findViewsInArea(rect), but I don't really know how to get the result I need.
You don't have to use setDimensions(). Just do something like
rect.on('change:position', function(evt, pos, y) {
paper.fitToContent({
minWidth: 801,
minHeight: 496
});
});
I'm trying to make a sub navigation menu animate a fixed position change after a user has scrolled down 200 pixels from the top. It works but it's very buggy, like when the user scrolls back to the top it doesn't always return to the original position, etc. I'm not strong with javascript / jquery, but I thought this would be simple to do. What am I missing?
Here's my fidde:
http://jsfiddle.net/visevo/bx67Z/
and a code snippet:
(function() {
console.log( "hello" );
var target = $('#side-nav');
var scrollDis = 200;
var reset = 20;
var speed = 500;
$(window).scroll(function() {
console.log( $(window).scrollTop() );
if( $(window).scrollTop() > scrollDis ) {
$(target).animate({
top: reset + 'px'
}, speed);
} else {
$(target).animate({
top: scrollDis + 'px'
}, speed);
}
});
})();
How about a little bit of css and jquery both ??
What I did is added transition to side-nav to animate it and rectified your js to just change it's css. You can set how fast it moves by changing the time in transition.
FIDDLE
#side-nav {
position: fixed;
top: 100px;
left: 10px;
width: 100px;
background: #ccc;
-webkit-transition:all 0.5s ease-in-out;
}
(function () {
var target = $('#side-nav');
var scrollDis = 100;
var reset = 20;
var speed = 500;
$(window).scroll(function () {
if ($(this).scrollTop() >= scrollDis) {
target.css("top", reset);
} else {
target.css("top", scrollDis);
}
});
})();
NOTE: When you cache a jQuery object like this
var target = $("#side-nav");
You don't need to use $ again around the variable.
Since I am commenting all over the place I should probably actually contribute an answer.
The issue is that you are adding scroll events every time a scroll occurs, which is causing more scrolling to occur, which causes more scroll events, hence infinite loop. While cancelling previous events will fix the problem, it's cleaner to only fire the event when you pass the threshold, IE:
(function () {
console.log("hello");
var target = $('#side-nav');
var scrollDis = 200;
var reset = 20;
var speed = 500;
var passedPosition = false;
var bolMoving = false;
$(window).scroll(function () {
if (bolMoving) return; // Cancel double calls.
console.log($(window).scrollTop());
if (($(window).scrollTop() > scrollDis) && !passedPosition) {
bolMoving = true; //
$(target).animate({
top: reset + 'px'
}, speed, function() { bolMoving = false; passedPosition = true; });
} else if (passedPosition && $(window).scrollTop() <= scrollDis) {
bolMoving = true;
$(target).animate({
top: scrollDis + 'px'
}, speed, function() { bolMoving = false; passedPosition = false; });
}
});
})();
http://jsfiddle.net/bx67Z/12/
http://jsfiddle.net/bx67Z/3/
I just added .stop() in front of the .animate() , and it works a lot better already.
$(target).stop().animate({
top: reset + 'px'
}, speed);
} else {
$(target).stop().animate({
top: scrollDis + 'px'
}, speed);
You can also use .stop(true)
http://jsfiddle.net/bx67Z/5/
$(target).stop(true).animate({
top: reset + 'px'
}, speed);
} else {
$(target).stop(true).animate({
top: scrollDis + 'px'
}, speed);
You can also use .stop(true, true)
http://jsfiddle.net/bx67Z/4/
$(target).stop(true, true).animate({
top: reset + 'px'
}, speed);
} else {
$(target).stop(true, true).animate({
top: scrollDis + 'px'
}, speed);
So the reason .stop(true) works so well, is that it clears the animation queue. The reason yours was being "buggy" is because on every scroll the animation queue was "bubbling up" , thus it took a long time for it to reach the point where it would scroll back to the original position.
For information about .stop() , see here http://api.jquery.com/stop