I'm trying to create a parallax effect in firefox by modifying the top position of elements using the onscroll event. I throttled the onscroll event so it doesn't overload the browser and I added a transition top property in the css to make things smoother. This works pretty well in every browser, but firefox is extremely choppy for some reason. Is there any way to make this transition smoother?
window.onscroll = throttle(function(){
var scrollDistance = window.pageYOffset || window.document.documentElement.scrollTop || window.document.body.scrollTop;
document.getElementById("back").style.top = -scrollDistance * 0.3 + "px";
document.getElementById("mid").style.top = -scrollDistance * 0.5 + "px";
document.getElementById("fore").style.top = -scrollDistance * 0.9 + "px";
}, 100);
function throttle (callback, limit) {
var wait = false;
return function () {
if (!wait) {
callback.call();
wait = true;
setTimeout(function () {
wait = false;
}, limit);
}
}
}
body{
height: 5000px;
background: url(http://lorempixel.com/output/city-q-c-1920-1920-4.jpg);
}
.parallaxEl {
width: 1920px;
height: 1080px;
position: fixed;
transition: top 0.1s;
}
#back{
background: url(http://wall.rimbuz.com/wp-content/uploads/4K-Wide-Wallpapers.jpg);
}
#mid{
background: url(https://wallpaperscraft.com/image/space_planet_background_83807_3840x2160.jpg);
}
#fore{
background: url(http://wall.rimbuz.com/wp-content/uploads/4K-HD-Background-Wallpapers.jpg);
}
<body>
<div class="parallaxEl" id="back"></div>
<div class="parallaxEl" id="mid"></div>
<div class="parallaxEl" id="fore"></div>
</body>
http://codepen.io/anon/pen/NAzBrX
Using requestAnimationFrame, you'll get a smoother throttler.
requestAnimationFrame has the advantage to be synced with the screen refresh rate.
Here is a code demo :
// your callback
var scrollHandler = function() {
var scrollDistance = window.pageYOffset || window.document.documentElement.scrollTop || window.document.body.scrollTop;
document.getElementById("back").style.top = -scrollDistance * 0.3 + "px";
document.getElementById("mid").style.top = -scrollDistance * 0.5 + "px";
document.getElementById("fore").style.top = -scrollDistance * 0.9 + "px";
};
// the throttle function
// returns the function that should be passed has an event listener
var throttle = function(callback) {
// a simple flag
var active = false;
// to keep track of the last event
var evt;
// fired only when screen has refreshed
var handler = function(){
// release our flag
active = false;
// call the callback
callback(evt);
}
// the actual event handler
return function handleEvent(e) {
// save our event at each call
evt = e;
// only if we weren't already doing it
if (!active) {
// raise the flag
active = true;
// wait for next screen refresh
requestAnimationFrame(handler);
};
}
}
// remember to call the function, we need its returned function
window.onscroll = throttle(scrollHandler);
body {
height: 5000px;
background: url(http://lorempixel.com/output/city-q-c-1920-1920-4.jpg);
}
.parallaxEl {
width: 1920px;
height: 1080px;
position: fixed;
transition: top 0.1s;
}
#back {
background: url(http://wall.rimbuz.com/wp-content/uploads/4K-Wide-Wallpapers.jpg);
}
#mid {
background: url(https://wallpaperscraft.com/image/space_planet_background_83807_3840x2160.jpg);
}
#fore {
background: url(http://wall.rimbuz.com/wp-content/uploads/4K-HD-Background-Wallpapers.jpg);
}
<body>
<div class="parallaxEl" id="back"></div>
<div class="parallaxEl" id="mid"></div>
<div class="parallaxEl" id="fore"></div>
</body>
The event 'onscroll' is caused after a scroll, and the event 'onmousewheel' ('onwheel') is caused after a scroll. For a scroll using a mouse animation will be more smoothly.
Example here: excube.hol.es
When possible you should use CSS 3D Transforms for animation because it has access to a users GPU and allows for smoother animation.
Hardware Acceleration and CSS Animation
https://www.sitepoint.com/introduction-to-hardware-acceleration-css-animations/
Also, here is an example of a basic parallax effect using this technique...
https://css-tricks.com/tour-performant-responsive-css-site/#article-header-id-2
Related
I tried to make zoom in Zoom out on scroll. The element is zooming on scrolling but I want to make it(the zoomed element) go up once it reached the full width of the screen.
Here's my code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
.zoom{
height:100vh;
width:100%;
display:grid;
place-items:center;
position:fixed;
top:0;
left:0;
background: url('img/bg.png');
}
.afterzoom{
height: 200vh;
background: red;
}
</style>
</head>
<body>
<div class="zoom">
<!-- <h1>Zoom meeeee</h1> -->
<img src="img/square.png" alt="">
</div>
<div class="afterzoom"></div>
<script>
const zoomElement = document.querySelector(".zoom");
let zoom = 1;
const ZOOM_SPEED = 0.1;
document.addEventListener("wheel", function(e) {
if(e.deltaY > 0){
if (zoomElement.style.transform >= `scale(5)`) {
console.log("now scroll down");
return false;
}
zoomElement.style.transform = `scale(${zoom += ZOOM_SPEED})`;
}else{
if (zoomElement.style.transform == `scale(1)`) {
// console.log("minus");
return false;
}
zoomElement.style.transform = `scale(${zoom -= ZOOM_SPEED})`; }
});
</script>
</body>
</html>
Fiddle: https://jsfiddle.net/mayureshpitale/6etpn0vs/3/
I am trying to make something like this: http://truegossiper.com/vilson17/
A bit of a different approach using the scroll event and window.scrollY for positioning together with requestAnimationFrame (RAF).
This also works, if the image (or other content) is not squared.
The problem with the wheel event is, that it does not trigger when the user uses the scrollbar or arrow keys to scroll the page.
The problem with the scroll event is, that it executes a lot... that's why RAF is used, to only execute necessary zoom changes.
Keep in mind, this exact code works with the document width when the page is loaded. If the user resizes the window or changes the browsers zoom, the element will resize to the same scale as before. You could fix this with some extra code by using a ResizeObserver. (Remove the existing event listener, resize the Image and execute parts of the code again, every time the document is resized...)
You will notice this, when you try to view the below snippet as full page.
const zoomElement = document.querySelector('.zoom')
const fadeElement = document.querySelector('.fade')
const afterZoomElement = document.querySelector('.afterzoom')
const imgElement = document.querySelector('img')
const WIDTH = document.body.clientWidth
const HEIGHT = zoomElement.clientHeight
const IMAGE_WIDTH = imgElement.clientWidth
const IMAGE_HEIGHT = imgElement.clientHeight
const ZOOM_SPEED = 100 // Lower is faster
const ZOOM_BREAKPOINT = WIDTH / IMAGE_WIDTH // When it should stop zooming in
const IMAGE_HEIGHT_MAX = IMAGE_HEIGHT * ZOOM_BREAKPOINT
const ABSOLUTE = ZOOM_BREAKPOINT * ZOOM_SPEED // Absolute position, when the Element reached maximum size
// Fade --------------------------------------------------------------------------------------
const FADE_SPEED = 500 // Lower is faster
let fade = 1
let prev = 0
// -------------------------------------------------------------------------------------- Fade
function anim() {
let scroll = window.scrollY
let temp = scroll / ZOOM_SPEED
let zoom = temp > 1 ? temp : 1
// Only update the Elements scale, when we are below the breakpoint
if (zoom < ZOOM_BREAKPOINT) {
// Only scale the Image, so the Zoom element does not mess with the document width
imgElement.style.transform = `scale(${zoom})`
// Sets the Elements position to fixed, so it can resize without scrolling away
zoomElement.style.top = '0px'
zoomElement.style.position = 'fixed'
} else {
// Makes sure the Element always reaches Max Size
imgElement.style.transform = `scale(${ZOOM_BREAKPOINT})`
// Sets the elements position to absolute, so it will scroll with the rest of the document
zoomElement.style.position = 'absolute'
zoomElement.style.top = ABSOLUTE + 'px'
}
// Fade --------------------------------------------------------------------------------------
let dif = prev - scroll
if (zoom < ZOOM_BREAKPOINT - FADE_SPEED / ZOOM_SPEED) {
fade = 1
} else if (zoom > ZOOM_BREAKPOINT) {
fade = 0
} else {
fade += dif / FADE_SPEED
}
fadeElement.style.opacity = fade
prev = scroll
// -------------------------------------------------------------------------------------- Fade
}
// Resets scroll position on every reload
if ('scrollRestoration' in history) {
history.scrollRestoration = 'manual'
}
document.addEventListener('scroll', () => window.requestAnimationFrame(anim))
// Fade --------------------------------------------------------------------------------------
zoomElement.style.opacity = 1
// -------------------------------------------------------------------------------------- Fade
// Positions the afterZoom element right below the zoomed image
afterZoomElement.style.top = ABSOLUTE + IMAGE_HEIGHT_MAX / 2 + HEIGHT / 2 + 'px'
body {
margin: 0;
}
img {
width: 150px;
height: 150px;
background-color: black;
}
.fade {
height: 100vh;
width: 100%;
position: fixed;
top: 0;
left: 0;
background: blue;
}
.zoom {
height: 100vh;
width: 100%;
display: grid;
place-items: center;
position: fixed;
top: 0;
left: 0;
}
.afterzoom {
position: absolute;
height: 200vh;
width: 100%;
background: red;
overflow-x: auto;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<div class="fade"></div>
<div class="zoom">
<img src="https://via.placeholder.com/150" alt="">
</div>
<div class="afterzoom">
<p>This should appear after the above element is fully zoomed.</p>
</div>
</body>
I got a bit carried away during coding and added some fading-in and -out to the blue background. Not necessary, but looks nice. You can remove the Fade ----- Fade parts of the code, without affecting functionality.
To alter zoom and fade speed, simply change the ZOOM_SPEED and FADE_SPEED variables.
My implementation,
http://kodhus.com/kodnest/land/PpNFTgp
I am curious, as I am not able for some reason to figure this out, how to get my JavaScript to make my slider behave more natural and smoother, if someone knows, how to, or can make this, feel free. I'd be happy to understand.
JavaScript:
const thumb = document.querySelector('.thumb');
const thumbIndicator = document.querySelector('.thumb .thumb-indicator');
const sliderContainer = document.querySelector('.slider-container');
const trackProgress = document.querySelector('.track-progress');
const sliderContainerStart = sliderContainer.offsetLeft;
const sliderContainerWidth = sliderContainer.offsetWidth;
var translate;
var dragging = false;
var percentage = 14;
document.addEventListener('mousedown', function(e) {
if (e.target.classList.contains('thumb-indicator')) {
dragging = true;
thumbIndicator.classList.add('focus');
}
});
document.addEventListener('mousemove', function(e) {
if (dragging) {
console.log('moving', e)
if (e.clientX < sliderContainerStart) {
translate = 0;
} else if (e.clientX > sliderContainerWidth + sliderContainerStart) {
translate = sliderContainerWidth;
} else {
translate = e.clientX - sliderContainer.offsetLeft;
}
thumb.style.transform = 'translate(-50%) translate(' + translate + 'px)';
trackProgress.style.transform = 'scaleX(' + translate / sliderContainerWidth + ')'
}
});
function setPercentage() {
thumb.style.transform = 'translate(-50%) translate(' + percentage/100 * sliderContainerWidth + 'px)';
trackProgress.style.transform = 'scaleX(' + percentage/100 + ')';
}
function init() {
setPercentage();
}
init();
document.addEventListener('mouseup', function(e) {
dragging = false;
thumbIndicator.classList.remove('focus');
});
EDIT: Is there a way to smoothly and naturally increment by one for every slow move?
Is it possible to make to behave as if, like when one clicks the progress bar so that it jumps there?
The kodhus site is very janky in my browser, so I can't tell if your code lacks responsiveness or whether it's the site itself. I feel that your code is a bit convoluted: translate and width / height are mixed unnecessarily; no need to use a dragging boolean when that information is always stored in the classlist. The following slider performs nicely, and has a few considerations I don't see in yours:
stopPropagation when clicking the .thumb element
drag stops if window loses focus
pointer-events: none; applied to every part of the slider but the .thumb element
let applySliderFeel = (slider, valueChangeCallback=()=>{}) => {
// Now `thumb`, `bar` and `slider` are the elements that concern us
let [ thumb, bar ] = [ '.thumb', '.bar' ].map(v => slider.querySelector(v));
let changed = amt => {
thumb.style.left = `${amt * 100}%`;
bar.style.width = `${amt * 100}%`;
valueChangeCallback(amt);
};
// Pressing down on `thumb` activates dragging
thumb.addEventListener('mousedown', evt => {
thumb.classList.add('active');
evt.preventDefault();
evt.stopPropagation();
});
// Releasing the mouse button (anywhere) deactivates dragging
document.addEventListener('mouseup', evt => thumb.classList.remove('active'));
// If the window loses focus dragging also stops - this can be a very
// nice quality of life improvement!
window.addEventListener('blur', evt => thumb.classList.remove('active'));
// Now we have to act when the mouse moves...
document.addEventListener('mousemove', evt => {
// If the drag isn't active do nothing!
if (!thumb.classList.contains('active')) return;
// Compute `xRelSlider`, which is the mouse position relative to the
// left side of the slider bar. Note that *client*X is compatible with
// getBounding*Client*Rect, and using these two values we can quickly
// get the relative x position.
let { width, left } = slider.getBoundingClientRect();
// Consider mouse x, subtract left offset of slider, and subtract half
// the width of the thumb (so drags position the center of the thumb,
// not its left side):
let xRelSlider = evt.clientX - left - (thumb.getBoundingClientRect().width >> 1);
// Clamp `xRelSlider` between 0 and the slider's width
if (xRelSlider < 0) xRelSlider = 0;
if (xRelSlider > width) xRelSlider = width;
// Apply styling (using percents is more robust!)
changed(xRelSlider / width);
evt.preventDefault();
evt.stopPropagation();
});
slider.addEventListener('mousedown', evt => {
let { width, left } = slider.getBoundingClientRect();
// Clicking the slider also activates a drag
thumb.classList.add('active');
// Consider mouse x, subtract left offset of slider, and subtract half
// the width of the thumb (so drags position the center of the thumb,
// not its left side):
let xRelSlider = evt.clientX - left - (thumb.getBoundingClientRect().width >> 1);
// Apply styling (using percents is more robust!)
changed(xRelSlider / width);
evt.preventDefault();
evt.stopPropagation();
});
changed(0);
};
let valElem = document.querySelector('.value');
applySliderFeel(document.querySelector('.slider'), amt => valElem.innerHTML = amt.toFixed(3));
.slider {
position: absolute;
width: 80%; height: 4px; background-color: rgba(0, 0, 0, 0.3);
left: 10%; top: 50%; margin-top: -2px;
}
.slider > .bar {
position: absolute;
left: 0; top: 0; width: 0; height: 100%;
background-color: #000;
pointer-events: none;
}
.slider > .thumb {
position: absolute;
width: 20px; height: 20px; background-color: #000; border-radius: 100%;
left: 0; top: 50%; margin-top: -10px;
}
.slider > .thumb.active {
box-shadow: 0 0 0 5px rgba(0, 0, 0, 0.5);
}
<div class="slider">
<div class="bar"></div>
<div class="thumb"></div>
</div>
<div class="value"></div>
I want to achieve when an image is clicked then it will enlarge for a set amount of time. So far I'm up to here in my JS but it doesn't work
var image = document.getElementById('pic');
function enlarge() {
image.style.height="600px";
}
image.onclick =enlarge;
After I tried to implement.
var image = document.getElementById('pic');
function enlarge() {
image.style.height="600px";
}
image.onclick = setInterval(enlarge; 1000);
How should I implement this? JSFIDDLE
Using setInterval
We want 60fps, so each frame would be 1000 / 60 which equals about 16.667ms long. We need to enlarge the height by 500px. 500 / 60 equals to 8.334. So we need an interval of 16.667 ms, and every iteration, enlarge the image by 8.334px, and stop when the height reaches 600px:
var images = document.querySelectorAll('.pic');
images.forEach(function(image) {
image.addEventListener('click', enlarge);
});
function enlarge(e) {
var image = e.target;
var interval;
var height = 100;
interval = setInterval(function() {
height += 8.334;
if(height >= 600) {
height = 600;
clearInterval(interval);
}
image.style.height = height + 'px';
}, 16.667);
}
.pic {
width: 100px;
height: 100px;
vertical-align: top;
}
<img src='https://placehold.it/100x100' class='pic'>
<img src='https://placehold.it/100x100' class='pic'>
Using requestAnimationFrame
A better way of doing it, will use requestAnimationFrame() that produces smoother animations. According to MDN:
The window.requestAnimationFrame() method tells the browser that you
wish to perform an animation and requests that the browser call a
specified function to update an animation before the next repaint.
The math stays the same, but requestAnimationFrame will handle the calling the next frame after 16.667ms.
var images = document.querySelectorAll('.pic');
images.forEach(function(image) {
image.addEventListener('click', enlarge);
});
function enlarge(e) {
var image = e.target;
var interval;
var height = 100;
function enlargeInner() {
height += 8.334;
if(height >= 600) {
height = 600;
}
image.style.height = height + 'px';
height < 600 && requestAnimationFrame(enlargeInner);
}
enlargeInner();
}
.pic {
width: 100px;
height: 100px;
vertical-align: top;
}
<img src='https://placehold.it/100x100' class='pic'>
<img src='https://placehold.it/100x100' class='pic'>
You just assign same height in an interval. You need to increment it, like:
image.style.height = (+image.style.height + 600) + "px";
But I guess it is not your goal, as it will make your image grow 600px every second. I think what you are looking for is just making it bigger to actual point of size? If so, try using CSS transition combined with javascript, like:
CSS:
img {
width: 300px;
height: 300px;
-webkit-transition: width 1s linear, height 1s linear;
transition: width 1s linear, height 1s linear;
}
.enlarged {
width: 600px;
height: 600px;
}
JS:
document.getElementById('pic').addEventListener("click", function(e){
this.classList.toggle("enlarged");
}
setInterval() only won't work.
Basically what your code is doing, that it waits 1000 milliseconds, and runs the enlarge function.
(by the way, you have a typo, at the last line, there should be a comma between enlarge and 1000)
The way I would do it is to add a css class with an animation, and then add that class to the image on click.
let myImg = document.getElementById("img1");
myImg.addEventListener("click", () => myImg.classList.toggle("enlarge"));
/*
The code above is using ES6 (the newest version of JavaScript) and and a thing called an arrow function. If you don't get it, here is the "normal way" to do it. It will do exactly the same as the above code.
var myImg = document.getElementById("img1");
myImg.addEventListener("click", function(){
myImg.classList.toggle("enlarge")
});
*/
#img1 {
transition: 1s
}
.enlarge {
width: 300px;
height: 300px;
}
<img id="img1" src="https://foswiki.org/pub/Support/Glossary/600px-Example.svg.png" width="100px" height="100px">
I forked your fiddle.
You need to change the way you're approaching the click event like so:
function enlarge() {
setInterval(function() {
// do stuff
}, 1000)
}
image.onclick = enlarge;
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'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