I have a page that only uses this excellent parallax function and I don't want to load jQuery just for that.
Can you write this function in plain javascript and keep it small and readable? (Has to work in IE10+, modern browsers)
$(document).ready(function(){
function draw() {
requestAnimationFrame(draw);
// Drawing code goes here
scrollEvent();
}
draw();
});
function scrollEvent(){
if(!is_touch_device()){
viewportTop = $(window).scrollTop();
windowHeight = $(window).height();
viewportBottom = windowHeight+viewportTop;
if($(window).width())
$('[data-parallax="true"]').each(function(){
distance = viewportTop * $(this).attr('data-speed');
if($(this).attr('data-direction') === 'up'){ sym = '-'; } else { sym = ''; }
$(this).css('transform','translate3d(0, ' + sym + distance +'px,0)');
});
}
}
function is_touch_device() {
return 'ontouchstart' in window // works on most browsers
|| 'onmsgesturechange' in window; // works on ie10
}
You can do what you're asking here by looking at You Might Not Need jQuery.
Your code, translated to vanilla javascript, should be something like this:
document.addEventListener('DOMContentLoaded', function() {
function draw() {
requestAnimationFrame(draw);
// Drawing code goes here
scrollEvent();
}
draw();
});
function scrollEvent() {
if (!is_touch_device()){
var viewportTop = window.scrollY;
var windowHeight = document.documentElement.clientHeight;
var viewportBottom = windowHeight + viewportTop;
if (document.documentElement.clientWidth) {
var parallax = document.querySelectorAll('[data-parallax="true"]');
for (var i = 0; i < parallax.length; i++) {
var item = parallax[i];
var distance = viewportTop * item.getAttribute('data-speed');
var sym = item.getAttribute('data-direction') === 'up' ? '-' : '';
item.style.transform = 'translate3d(0, ' + sym + distance +'px,0)';
}
}
}
}
function is_touch_device() {
return 'ontouchstart' in window || 'onmsgesturechange' in window;
}
Related
I am using this code: EXAMPLE
Depending on if "image-ul" is fully above the bottom edge of the browser window or not, will make divs scroll at different speeds, as it should. But the problem that I am having is that the scrolling is not smooth when the slow scrolling divs get somewhere close to the top of the page. They seem to stall for a moment, and even scroll in the opposite direction sometimes.
//
// default speed ist the lowest valid scroll speed.
//
var default_speed = 1;
//
// speed increments defines the increase/decrease of the acceleration
// between current scroll speed and data-scroll-speed
//
var speed_increment = 0.01;
//
// maximum scroll speed of the elements
//
var data_scroll_speed_a = 3; // #sloganenglish
var data_scroll_speed_b = 5; // #image-ul
//
//
//
var increase_speed, decrease_speed, target_speed, current_speed, speed_increments;
$(document).ready(function() {
$(window).on('load resize scroll', function() {
var WindowScrollTop = $(this).scrollTop(),
Div_one_top = $('#image-ul').offset().top,
Div_one_height = $('#image-ul').outerHeight(true),
Window_height = $(this).outerHeight(true);
if (WindowScrollTop + Window_height >= (Div_one_top + Div_one_height)) {
$('#sloganenglish').attr('data-scroll-speed', data_scroll_speed_a).attr('data-current-scroll-speed', default_speed).attr('data-speed-increments', data_scroll_speed_a * speed_increment);
$('#image-ul').attr('data-scroll-speed', data_scroll_speed_b).attr('data-current-scroll-speed', default_speed).attr('data-speed-increments', data_scroll_speed_b * speed_increment);
$('.slogan-a-line').css('color', 'yellow');
increase_speed = true;
decrease_speed = false;
} else {
$('#sloganenglish').attr('data-scroll-speed', '1').attr('data-current-scroll-speed', default_speed);
$('#image-ul').attr('data-scroll-speed', '1').attr('data-current-scroll-speed', default_speed);
$('.slogan-a-line').css('color', 'red');
decrease_speed = true;
increase_speed = false;
}
}).scroll();
});
// data-scroll-speed script
$.fn.moveIt = function() {
var $window = $(window);
var instances = [];
$(this).each(function() {
instances.push(new moveItItem($(this)));
});
window.onscroll = function() {
var scrollTop = $window.scrollTop();
instances.forEach(function(inst) {
inst.update(scrollTop);
});
}
}
var moveItItem = function(el) {
this.el = $(el);
this.speed = parseInt(this.el.attr('data-scroll-speed'));
this.current_speed = 1.0;
};
moveItItem.prototype.update = function(scrollTop) {
target_speed = parseInt(this.el.attr('data-scroll-speed'));
current_speed = this.current_speed;
speed_increments = parseFloat(this.el.attr('data-speed-increments'));
if (increase_speed) {
if (current_speed < target_speed) {
current_speed += speed_increments;
} else {
current_speed = target_speed;
}
} else if (decrease_speed) {
if (current_speed > default_speed) {
current_speed -= speed_increments;
}
if ($(window).scrollTop() === 0) {
current_speed = default_speed;
}
}
this.current_speed = current_speed;
var pos = scrollTop / this.current_speed;
this.el.css('transform', 'translateY(' + -pos + 'px)');
};
// Initialization
$(function() {
$('[data-scroll-speed]').moveIt();
});
The sample code wasn't slow for me, so it may be specific to your machine or browser.
However, there are a few things you can do:
Don't use jQuery where you don't need it. jQuery is significantly slower than using native JS functions (e.g. document.getElementById).
Don't repeatedly use jQuery selectors. Every time you use a jQuery selector, you suffer a performance hit. So for example, instead of this:
function(){
var Div_one_top = $('#image-ul').offset().top,
Div_one_height = $('#image-ul').outerHeight(true);
}
Do this:
var imageUl = $('#image-ul');
function(){
imageUl.offset().top,
imageUl.outerHeight(true);
}
This example should increase performance quite a bit. You're doing multiple jQuery selectors every time the page scrolls for no reason.
The best choice for something performance intensive is to cut out jQuery completely and do it by hand.
So for one of my new projects, I decided to write a super simple parallax script for some background images on scroll. This is what I came up with:
$(document).ready(function(){
parallaxScroll();
$(window).bind('scroll', function() {
parallaxScroll();
});
});
function parallaxScroll() {
$(".parallax").each(function() {
if($(this).hasClass('reverse')) {
$(this).css("background-position","center " + (($(this).offset().top - $(window).scrollTop())/2) + "px");
} else {
$(this).css("background-position","center " + (($(this).offset().top - $(window).scrollTop())/-2) + "px");
}
});
}
My question is, is this efficient enough? If not, is there a better solution? I wasn't sure if using an .each() would be best for performance, but it seems to work fine. The reason I have the function run at document load is so when you scroll the page for the first time, the background image doesn't jump.
Instead of css which sets the value immediately, consider using animate instead. It defers setting values using timers/requestAnimationFrame, ensuring that your animation does not block the UI, is async (runs pseudo-parallel to other code), and ensures that the animation is smooth.
This is a plain JS solution, but you'll be able to port it to jQuery really easily:
var lastScrollY = 0;
var backgroundImageY = 0;
var requestAnimationFrame = window.requestAnimationFrame ||
window.msRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame;
window.addEventListener('load', processScrollEvent);
function processScrollEvent() {
var innerHeight = window.innerHeight;
var scrollHeight = document.body.scrollHeight;
var backgroundImage = document.querySelector('#background img');
lastScrollY = document.body.scrollTop;
var currBackgroundImageY = Math.round(((backgroundImage.scrollHeight - innerHeight) / 100) * ((lastScrollY / (innerHeight - scrollHeight)) * 100));
if(currBackgroundImageY != backgroundImageY) {
backgroundImageY = currBackgroundImageY;
requestAnimationFrame(processScrollAnimationFrame);
}
}
function processScrollAnimationFrame() {
var backgroundImage = document.querySelector('#background img');
var transforms = ['transform', 'oTransform', 'msTransform', 'mozTransform', 'webkitTransform'];
for(var i = 0; i < transforms.length; i++) {
backgroundImage.style[transforms[i]] = 'translate3d(0, ' + backgroundImageY + 'px, 0)';
}
}
Can someone tell me how to adjust this .js? I want it to be able to apply on H1 and H2 with an ID in my html. Now it is only working on css background-image. Below is the .js I'm using. I'm a beginner with javascript;)
Hope you guys can help me out!
(function( $ ){
var $window = $(window);
var windowHeight = $window.height();
$window.resize(function () {
windowHeight = $window.height();
});
$.fn.parallax = function(xpos, speedFactor, outerHeight) {
var $this = $(this);
var getHeight;
var firstTop;
var paddingTop = 0;
//get the starting position of each element to have parallax applied to it
$this.each(function(){
firstTop = $this.offset().top;
});
if (outerHeight) {
getHeight = function(jqo) {
return jqo.outerHeight(true);
};
} else {
getHeight = function(jqo) {
return jqo.height();
};
}
// setup defaults if arguments aren't specified
if (arguments.length < 1 || xpos === null) xpos = "50%";
if (arguments.length < 2 || speedFactor === null) speedFactor = 0.1;
if (arguments.length < 3 || outerHeight === null) outerHeight = true;
// function to be called whenever the window is scrolled or resized
function update(){
var pos = $window.scrollTop();
$this.each(function(){
var $element = $(this);
var top = $element.offset().top;
var height = getHeight($element);
// Check if totally above or totally below viewport
if (top + height < pos || top > pos + windowHeight) {
return;
}
$this.css('backgroundPosition', xpos + " " + Math.round((firstTop - pos) * speedFactor) + "px");
});
}
$window.bind('scroll', update).resize(update);
update();
};
})(jQuery);
It should be background-position. Can you try changing?
$this.css('background-position', xpos + " " + Math.round((firstTop - pos) * speedFactor) + "px");
i used this http://www.netmagazine.com/tutorials/create-interactive-street-view-jquery tutorial to create an intro for one of our customers:
http://f-bilandia.de/kunstmann/bronski/
It used to work really good on all browsers. When I updated to the newest stable version of Firefox (FF 18.0.1) however, there is heavy flickering while changing the images.
When reading the release notes of the newest version, i saw that ff has a new Javascript engine and has improved image quality with a new HTML scaling algorithm. Maybe it's because of that? Other possible solutions?
Below you can see the code i've used:
$(document).ready(function(){
var $doc = $(document);
var $win = $(window);
// dimensions - we want to cache them on window resize
var windowHeight, windowWidth;
var fullHeight, scrollHeight;
var streetImgWidth = 1024, streetImgHeight = 640;
calculateDimensions();
var currentPosition = -1, targetPosition = 0;
var $videoContainer = $('.street-view');
var video = $('.street-view > img')[0];
var $hotspotElements = $('[data-position]');
// handling resize and scroll events
function calculateDimensions() {
windowWidth = $win.width();
windowHeight = $win.height();
fullHeight = $('#main').height();
scrollHeight = fullHeight - windowHeight;
}
function handleResize() {
calculateDimensions();
resizeBackgroundImage();
handleScroll();
}
function handleScroll() {
targetPosition = $win.scrollTop() / scrollHeight;
}
// main render loop
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(/* function */ callback, /* DOMElement */ element){
window.setTimeout(callback, 1000 / 60);
};
})();
function animloop(){
if ( Math.floor(currentPosition*5000) != Math.floor(targetPosition*5000) ) {
currentPosition += (targetPosition - currentPosition) / 5;
render(currentPosition);
}
requestAnimFrame(animloop);
}
// rendering
function render( position ) {
// position the elements
var minY = -windowHeight, maxY = windowHeight;
$.each($hotspotElements,function(index,element){
var $hotspot = $(element);
var elemPosition = Number( $hotspot.attr('data-position') );
var elemSpeed = Number( $hotspot.attr('data-speed') );
var elemY = windowHeight/2 + elemSpeed * (elemPosition-position) * scrollHeight;
if ( elemY < minY || elemY > maxY ) {
$hotspot.css({'visiblity':'none', top: '-1000px','webkitTransform':'none'});
} else {
$hotspot.css({'visiblity':'visible', top: elemY, position: 'fixed'});
}
});
renderVideo( position );
}
function resizeBackgroundImage(){
// get image container size
var scale = Math.max( windowHeight/streetImgHeight , windowWidth/streetImgWidth );
var width = scale * streetImgWidth , height = scale * streetImgHeight;
var left = (windowWidth-width)/2, top = (windowHeight-height)/2;
$videoContainer
.width(width).height(height)
.css('position','fixed')
.css('left',left+'px')
.css('top',top+'px');
}
// video handling
var imageSeqLoader = new ProgressiveImageSequence( "street/vid-{index}.jpg" , 387 , {
indexSize: 4,
initialStep: 16,
onProgress: handleLoadProgress,
onComplete: handleLoadComplete,
stopAt: 1
} );
// there seems to be a problem with ie
// calling the callback several times
var loadCounterForIE = 0;
imageSeqLoader.loadPosition(currentPosition,function(){
loadCounterForIE++;
if ( loadCounterForIE == 1 ) {
renderVideo(currentPosition);
imageSeqLoader.load();
imageSeqLoader.load();
imageSeqLoader.load();
imageSeqLoader.load();
}
});
var currentSrc, currentIndex;
function renderVideo(position) {
var index = Math.round( currentPosition * (imageSeqLoader.length-1) );
var img = imageSeqLoader.getNearest( index );
var nearestIndex = imageSeqLoader.nearestIndex;
if ( nearestIndex < 0 ) nearestIndex = 0;
var $img = $(img);
var src;
if ( !!img ) {
src = img.src;
if ( src != currentSrc ) {
video.src = src;
currentSrc = src;
}
}
}
$('body').append('<div id="loading-bar" style="">Loading...</div>');
function handleLoadProgress() {
var progress = imageSeqLoader.getLoadProgress() * 100;
$('#loading-bar').css({width:progress+'%',opacity:1});
}
function handleLoadComplete() {
$('#loading-bar').css({width:'100%',opacity:0,display: "none"});
$("html, body").css("overflow", "auto");
$("html, body").css("overflow-x", "hidden");
$("nav").css("display", "block");
$("#preloader").fadeOut("slow");
$("#scroll-hint").css("display", "block");
}
$win.resize( handleResize );
$win.scroll( handleScroll );
handleResize();
animloop();
});
Inside your "render( position )" function the following lines seem like they should be refactored.
if ( elemY < minY || elemY > maxY ) {
$hotspot.css({'visiblity':'none', top: '-1000px','webkitTransform':'none'});
} else {
$hotspot.css({'visiblity':'visible', top: elemY, position: 'fixed'});
}
For one visibility is spelled wrong and there is no "none" value for it (it would be "hidden"). Just use "display" with "none" and "" values.
The "top", "webkitTransform", and "position" keys seem unnecessary. If the element is not visible there's no need to set the top, and why wouldn't the element always be fixed position?
I am doing a small javascript animation. this is my code :
window.onload = function () {
var heading = document.getElementsByTagName('h1')[0];
heading.onclick = function () {
var divHeight = 250;
var speed = 10;
var myInterval = 0;
alert(divHeight);
slide();
function slide() {
if (divHeight == 250) {
myInterval = setInterval(slideUp, 30);
} else {
myInterval = setInterval(slideDwn, 30);
alert('i am called as slide down')
}
}
function slideUp() {
var anima = document.getElementById('anima');
if (divHeight <= 0) {
divHeight = 0;
anima.style.height = '0px';
clearInterval(myInterval);
} else {
divHeight -= speed;
if (divHeight < 0) divHeight = 0;
anima.style.height = divHeight + 'px';
}
}
function slideDwn() {
var anima = document.getElementById('anima');
if (divHeight >= 250) {
divHeight = 250;
clearInterval(myInterval);
} else {
divHeight += speed;
anima.style.height = divHeight + 'px';
}
}
}
}
i am using above code for simple animation. i need to get the result 250 on the first click, as well second click i has to get 0 value. but it showing the 250 with unchanged. but i am assigning the value to set '0', once the div height reached to '0'.
what is the issue with my code? any one help me?
Everytime you click on the div the divHeight variable is reset to 250, thus your code never calls slideDwn. Moving the divHeight declaration outside the event handler should do the trick.
Also, your div wont have the correct size when any of the 2 animations end. You're setting the divHeight variable to 250 or 0 correctly, but never actually setting anima.style.height after that.
I've rewritten your code into something simpler and lighter. The main difference here is that we're using a single slide() function here, and that the height of the div in question is stored in a variable beforehand to ensure that the element slides into the correct position.
Note that this is a very simplistic implementation and assumes that the div carries no padding. (The code uses ele.clientHeight and ele.style.height interchangeably, which admittedly, is a pretty bad choice, but is done here to keep the code simple)
var heading = document.getElementsByTagName('h1')[0],
anima = document.getElementById('anima'),
divHeight = anima.clientHeight,
speed = 10,
myInterval = 0,
animating = false;
function slide(speed, goal) {
if(Math.abs(anima.clientHeight - goal) <= speed){
anima.style.height = goal + 'px';
animating = false;
clearInterval(myInterval);
} else if(anima.clientHeight - goal > 0){
anima.style.height = (anima.clientHeight - speed) + 'px';
} else {
anima.style.height = (anima.clientHeight + speed) + 'px';
}
}
heading.onclick = function() {
if(!animating) {
animating = true;
var goal = (anima.clientHeight >= divHeight) ? 0 : divHeight;
myInterval = setInterval(slide, 13, speed, goal);
}
}
See http://www.jsfiddle.net/yijiang/dWJgG/2/ for a simple demo.
I've corrected your code a bit (See working demo)
window.onload = function () {
var heading = document.getElementsByTagName('h1')[0];
var anima = document.getElementById('anima');
var divHeight = 250;
heading.onclick = function () {
var speed = 10;
var myInterval = 0;
function slideUp() {
divHeight -= speed;
if (divHeight <= 0) {
divHeight = 0;
clearInterval(myInterval);
}
anima.style.height = divHeight + 'px';
}
function slideDwn() {
divHeight += speed;
if (divHeight >= 250) {
divHeight = 250;
clearInterval(myInterval);
}
anima.style.height = divHeight + 'px';
}
function slide() {
console.log(divHeight )
if (divHeight == 250) {
myInterval = setInterval(slideUp, 30);
} else {
myInterval = setInterval(slideDwn, 30);
}
}
slide();
}
}