Custom GSAP "hacked" mousewheel scroll - javascript
I made a custom content scroller with jQuery and GSAP in the aim to have a nicer effect than the Chrome basic & stuttered scroll. But I just saw (on Chrome then) that the scroll is laggy and well bugged when I test it with my Wacom tablet (with pen + horizontal scroll, or any trackpad/touch stuff I guess).
Any thoughts about tweaks to render a more natural scroll effect, not hacked like now?
Maybe it's something about the mousewheel DOMMouseScroll event, or my method is bad, the markup/css need some changes…?
$("section").on("mousewheel DOMMouseScroll", function (e) {
e.preventDefault();
var delta = e.originalEvent.wheelDelta / 120 || -e.originalEvent.detail; // Chrome || FF
// Move thumbs
TweenLite.to($("#photos"), 1.2, {
scrollLeft: $("#photos").scrollLeft() - parseInt(delta * 35),
ease: Expo.easeOut,
overwrite: 5,
onUpdate: move
});
});
—
http://jsfiddle.net/h66tatp6/
Many thanks for your lights!
Cooked up something. Not really sure if it fits your needs or if it is exactly the type of thing you were looking for, but perhaps it will give you some ideas.
Codepen.
JavaScript:
/*global TweenMax,Power2,Power4*/
var initIntervalID=null,initInterval=10;
var scrollBar=document.querySelector('.scrollbar');
var scrollBarHandle=document.querySelector('.scrollbar__handle');
var scrollBarBg=document.querySelector('.scrollbar__bg');
var container=document.querySelector('.container');
var images=document.querySelector('.images');
var thumbs=document.querySelectorAll('.thumb');
var numThumbs=thumbs.length;
var resizeID=null,resizeTimeout=400,resizeDuration=.4,resizeEase=Power2.easeOut;
var initDuration=.4,initEase=Power2.easeOut,initStagger=.1;
var duration=.4,ease=Power2.easeOut;
var force3D=true;
var windowWidth=window.innerWidth;
var windowHeight=window.innerHeight;
var thumbWidth=200,thumbHeight=200,thumbGutter=20;
var scrollBarBgHeight=10,scrollBarBgWidth=thumbWidth,scrollBarHandleWidth=20,scrollBarGutter=10;
var imagesPositions=[],iterator=0,percentages=[],scrollBarHandlePositions=[];
function init(){
initImages();
initScrollbar();
initPositions();
assignListeners();
}
function onMouseWheel(event){
var e=window.event||event;
var delta=Math.max(-1,Math.min(1,(e.wheelDelta|| -e.detail)));
if(delta>0){
iterator-=1;
if(iterator<0){
iterator=0;
TweenMax.to(images,duration*2,{bezier:{type:'thru',values:[{x:imagesPositions[iterator]+(thumbWidth+thumbGutter)*.1},{x:imagesPositions[iterator]}]},ease:ease});
TweenMax.killTweensOf(scrollBar);
TweenMax.to(scrollBar,duration,{autoAlpha:1,ease:ease});
TweenMax.to(scrollBarHandle,duration*2,{
transformOrigin:'left',
bezier:{type:'thru',values:[{scaleX:.2},{scaleX:1}]},
ease:ease,
onComplete:function(){ TweenMax.to(scrollBar,duration,{autoAlpha:0,ease:ease}); }
});
}else{
TweenMax.to(images,duration,{x:imagesPositions[iterator],ease:ease});
TweenMax.killTweensOf(scrollBar);
TweenMax.to(scrollBar,duration,{autoAlpha:1,ease:ease});
TweenMax.to(scrollBarHandle,duration,{
x:scrollBarHandlePositions[iterator],
ease:ease,
onComplete:function(){ TweenMax.to(scrollBar,duration,{autoAlpha:0,ease:ease}); }
});
}
}else{
iterator+=1;
if(iterator>numThumbs-1){
iterator=numThumbs-1;
TweenMax.to(images,duration*2,{bezier:{type:'thru',values:[{x:imagesPositions[iterator]-(thumbWidth+thumbGutter)*.1},{x:imagesPositions[iterator]}]},ease:ease});
TweenMax.killTweensOf(scrollBar);
TweenMax.to(scrollBar,duration,{autoAlpha:1,ease:ease});
TweenMax.to(scrollBarHandle,duration*2,{
transformOrigin:'right',
bezier:{type:'thru',values:[{scaleX:.2},{scaleX:1}]},
ease:ease,
onComplete:function(){ TweenMax.to(scrollBar,duration,{autoAlpha:0,ease:ease}); }
});
}else{
TweenMax.to(images,duration,{x:imagesPositions[iterator],ease:ease});
TweenMax.killTweensOf(scrollBar);
TweenMax.to(scrollBar,duration,{autoAlpha:1,ease:ease});
TweenMax.to(scrollBarHandle,duration,{
x:scrollBarHandlePositions[iterator],
ease:ease,
onComplete:function(){ TweenMax.to(scrollBar,duration,{autoAlpha:0,ease:ease}); }
});
}
}
return false;
}
function listenToMouseWheel(){
if(container.addEventListener){
container.addEventListener('mousewheel',onMouseWheel,false);
container.addEventListener('DOMMouseScroll',onMouseWheel,false);
}else{
container.attachEvent('onmousewheel',onMouseWheel);
}
}
function adjustContainerOnResize(){
windowWidth=window.innerWidth;
windowHeight=window.innerHeight;
TweenMax.to(container,resizeDuration,{
x:windowWidth*.5-thumbWidth*.5,
y:windowHeight*.5-thumbHeight*.5,
ease:resizeEase,
force3D:force3D
});
}
function onResize(){
clearTimeout(resizeID);
resizeID=setTimeout(adjustContainerOnResize,resizeTimeout);
}
function listenToResize(){
(window.addEventListener)?window.addEventListener('resize',onResize,false):window.attachEvent('onresize',onResize);
}
function assignListeners(){
listenToResize();
listenToMouseWheel();
}
function initPositions(){
for(var i=0; i<numThumbs; i+=1){
imagesPositions[i]=-i*(thumbWidth+thumbGutter);
percentages[i]=i*(100/numThumbs);
if(i===0){
scrollBarHandlePositions[i]=0;
}else if(i===numThumbs-1){
scrollBarHandlePositions[i]=scrollBarBgWidth-scrollBarHandleWidth;
}else{
scrollBarHandlePositions[i]=i*(scrollBarBgWidth/(numThumbs-1))-scrollBarHandleWidth*.5;
}
}
}
function initScrollbar(){
TweenMax.set(scrollBar,{
y:scrollBarGutter,
width:scrollBarBgWidth,
height:scrollBarBgHeight,
force3D:force3D
});
TweenMax.set(scrollBarHandle,{y:0,force3D:force3D});
TweenMax.to(scrollBar,initDuration,{delay:initStagger,opacity:1,ease:initEase});
}
function initImages(){
for(var i=0; i<numThumbs; i+=1){
TweenMax.set(thumbs[i],{x:i*(thumbWidth+thumbGutter),force3D:force3D});
}
var width=numThumbs*(thumbWidth+thumbGutter)-thumbGutter;
TweenMax.set(container,{y:windowHeight*.5-thumbHeight*.5,x:windowWidth*.5-thumbWidth*.5,height:thumbHeight+scrollBarGutter+scrollBarBgHeight,width:thumbWidth,force3D:force3D});
TweenMax.set(images,{height:thumbHeight,width:width,force3D:force3D});
TweenMax.staggerTo(thumbs,initDuration,{opacity:1,ease:initEase},initStagger);
}
initIntervalID=setInterval(function(){
if(document.readyState==="complete"){
clearInterval(initIntervalID);
init();
}
},initInterval);
Beware of ugly, non-refactored, highly un-tested, un-optimized code. Created only for fun. If I find time, may be I will build upon it later and include other mouse interactions such as mousedown, mousemove, mouseup as well as touch interactions such as touchstart, touchmove, touchend events. Should be fun. For now, use it as you may like.
Related
Scroll function works fast in mac and touchpad
I have a slider and I made a code that goes next and prev with mouse scroll. But it doesn't work well with mac and touchpad. how can I fix it? Can I put some delay or something I tried to use some setTimeout but it didn't work well or I didn't do it right. $('#body').on('mousewheel', function (event) { if (typeof event.originalEvent.wheelDeltaY === 'undefined') { console.log("could not find mouse deltas") } var deltaY = event.originalEvent.wheelDeltaY; var scrolledUp = deltaY < 0; var scrolledDown = deltaY > 0; if (scrolledDown) { if (swiper_color.activeIndex > 2) { goTop(); activeIndexToTwo(); } else { swiper_color.slidePrev(); swiper_image.slidePrev(); swiper_desc.slidePrev(); swiper_title.slidePrev(); swiper_jar.slidePrev(); } } if (scrolledUp) { if (swiper_color.activeIndex < 2) { swiper_color.slideNext(); swiper_image.slideNext(); swiper_desc.slideNext(); swiper_title.slideNext(); swiper_jar.slideNext(); } else { checkscroll(); } } });
I'd recommend throttling the function. You could write a throttle function, or use one from a library like Lodash (Lodash throttle docs) function yourFn (event) { // ... all of your js here // ... } $('#body').on('mousewheel', _.throttle(yourFn, 100)); The 100(ms) above means your function can only fire every 100ms, feel free to modify the time to your liking.
Continue function until mouse is released
I want to be able to run the below function continuously while the mouse is down. At the moment the function excecutes once per click. I want to be able to continue running the function while mousedown and stop when the mouse is released. $('#md').mousedown(function(e){ var pCords = e.pageY +e.pageY; var max = $(document).height()+$(document).width(); var dg = 2*( pCords / max ); $('#div').css({ 'transform':'rotate('+dg+'deg) '}); });
This issue has been addressed in: jQuery continuous mousedown It will involve setting up a flag that you can set to true when the first mousedown event occurs, and calling a function that only finishes once the flag is set back to false when the mouseup event occurs.
Easy way to do with time delay between execution of event handler: var timerId = 0; $('#md').mousedown(function(e){ timerId = setInterval(function() { var pCords = e.pageY +e.pageY; var max = $(document).height()+$(document).width(); var dg = 2*( pCords / max ); $('#div').css({ 'transform':'rotate('+dg+'deg) '}); }, 20); // specify the required time delay here }); $('#md').mouseup(function(e){ clearInterval(timerId); });
You can do it using a simple semaphore. Here's an example: var lock = true; $('#md').mousedown(function() { lock = false; setTimeout(function() { while(!lock) { // Do your stuff here } }, 0); }); $(document).mouseup(function() { lock = true; }); I hope this does the trick. The setTimeout function detaches the execution of the function and creates an asynchronous process - this makes your code non-blocking. I'm sure there are more sophisticated ways to solve this problem. It's just that I haven't had my coffee yet. :)
like this? making a global boolean variable to track if mouse is up, then looping your function while mouse is down on that element. var ismouseup = false; $(document).ready(function(){ $('#md').mousedown(function(e){ ismouseup = false; while(!ismouseup) { //do your thing } }); $('#md').mouseup(function(e){ ismouseup = true; } }); --edit: set ismouseup to true when mouseup. sorry.
You can use setTimeout. This will run every 1 sec when mousedown $('#md').on({ mousedown: function () { $(this).data('clicked', true); var process = function() { if ($(this).data('clicked')) { // Do your stuff setTimeout(process, 100); } }; process(); }, mouseup: function () { $(this).data('clicked', false); } });
As an option you can set the handler for mousemove in mousedown and remove in mouseup as in code function movehandler(e) { var pCords = e.pageY + e.pageY; var max = $('#md').height(); var dg = 20 * (pCords / max); $('#md').css({ 'transform': 'rotate(' + dg + 'deg) ' }); } $('#md').mousedown(function (e) { $('body').on('mousemove', movehandler); }); $('#md').mouseup(function (e) { $('body').off('mousemove'); }); $('body').mouseleave(function (e) { $('body').off('mousemove'); }); Jsfiffle Example is not ideally written but you can get the idea EDIT: Note that $('body').on('mousemove', might not be sufficient dependent on your markup.
Android + jQuery - second on swipeleft wont execute
I have to do some Special things for my Webpage to work on Android the correct way. Some Images are displayed (one visible, the other unvisible) and through swipe it should be possible to Change them. No Problem so far on all OS. But it also should be possible to zoom. Now Android starts to be Buggy. It stops the zoom-gesture because of the swipe callback. The callback itself doesn't Change the page because the view is zoomed, so there should be no break. Now I work arround through turning my swipeleft and swiperight off while two fingers touching the Display, and tourning back on if the fingers leave the Display. On First run I can swipe, then I can zoom with no break, but then I can't swipe anymore. The function to set the callbacks back on again is called, it set's the callbacks, but they won't be executed... Here's the code: app.utils.scroll = (function(){ var $viewport = undefined; var swipeDisabled = false; var init = function(){ $viewport = $('#viewport'); $viewport.mousewheel(mayChangePage); // On touchstart with two fingers, remove the swipe listeners. $viewport.on('touchstart', function (e) { if (e.originalEvent.touches.length > 1) { removeSwipe(); swipeDisabled = true; } }); // On touchend, re-define the swipe listeners, if they where removed through two-finger-gesture. $viewport.on('touchend', function (e) { if (swipeDisabled === true) { swipeDisabled = false; initSwipe(); } }); initSwipe(); } var mayChangePage = function(e){ // If page is not zoomed, change page (next or prev). if (app.utils.zoom.isZoomed() === false) { if (e.deltaY > 0) { app.utils.pagination.prev(e); } else { app.utils.pagination.next(e); } } // Stop scrolling page through mouse wheel. e.preventDefault(); e.stopPropagation(); }; var next = function (e) { // If page is not zoomed, switch to next page. if (app.utils.zoom.isZoomed() === false) { app.utils.pagination.next(e); } }; var prev = function (e) { // If page is not zoomed, switch to prev page. if (app.utils.zoom.isZoomed() === false) { app.utils.pagination.prev(e); } }; var initSwipe = function () { // Listen to swipeleft / swiperight-Event to change page. $viewport.on('swipeleft.next', next); $viewport.on('swiperight.prev', prev); }; var removeSwipe = function () { // Remove listen to swipeleft / swiperight-Event for changing page to prevent android-bug. $viewport.off('swipeleft.next'); $viewport.off('swiperight.prev'); }; $(document).ready(init); }()); Pastebin Any ideas what I can do to get the Events back on again? Thanks for all Ideas. Regards lippoliv
Fixed it: jQuery Mobile itself prevents the swipe Event if an handler is registered, to kill an "scroll". So I overwrote the $.event.special.swipe.scrollSupressionThreshold value and set it to 10000, to prevent jQueryMobile's preventDefault-call: $.event.special.swipe.scrollSupressionThreshold = 10000; Now my Code Looks like app.utils.scroll = (function(){ var $viewport = undefined; var swipeDisabled = false; var init = function(){ $viewport = $('#viewport'); $viewport.mousewheel(mayChangePage); // See #23. $.event.special.swipe.scrollSupressionThreshold = 10000; // Listen to swipeleft / swiperight-Event to change page. $viewport.on('swipeleft.next', next); $viewport.on('swiperight.prev', prev); } var mayChangePage = function(e){ // If page is not zoomed, change page (next or prev). if (app.utils.zoom.isZoomed() === false) { if (e.deltaY > 0) { app.utils.pagination.prev(e); } else { app.utils.pagination.next(e); } } // Stop scrolling page through mouse wheel. e.preventDefault(); e.stopPropagation(); }; var next = function (e) { // If page is not zoomed, switch to next page. if (app.utils.zoom.isZoomed() === false) { app.utils.pagination.next(e); } }; var prev = function (e) { // If page is not zoomed, switch to prev page. if (app.utils.zoom.isZoomed() === false) { app.utils.pagination.prev(e); } }; $(document).ready(init); }()); Thanks to Omar- who wrote with me several minutes / hours in the jquery IRC and gave some suggestions regarding overwriting Standard values for jQueryMobile.
Zoom and pinch events not fired in Desktop using javascript
Im trying to create a function for app idle timeout. I registered all touch events using Gesture object. Doing these static events are fired. But zoom, pinch and cursor move are not fired. Any sussestions? var count = 0; var max = 15; var myGesture = new MSGesture(); myGesture.target = document.body; document.body.addEventListener("pointerdown", function (evt) { // adds the current mouse, pen, or touch contact for gesture recognition myGesture.addPointer(evt.pointerId); }); document.body.addEventListener("MSGestureStart", eventListener, false); document.body.addEventListener("MSGestureEnd", eventListener, false); document.body.addEventListener("MSGestureChange", eventListener, false); document.body.addEventListener("MSInertiaStart", eventListener, false); document.body.addEventListener("MSGestureTap", eventListener, false); document.body.addEventListener("MSGestureHold", eventListener, false); setTimer(); function setTimer() { if (count == max) window.close(); else { count = count + 1; t = setTimeout(setTimer, 1000); } } function eventListener(evt) { clearTimeout(t); count = 0; setTimer(); } }
In your pointerdown handler you need to add code to reflect when two (or more) fingers are down. There you will be able to multi touch gestures like zoom or pinch edit: not mine, but take a look at this code http://pastebin.com/frAqdhGj
Have a JS function *constantly* listen for clicks?
This seems like something that should be really simple. I have a few images animating on a page, but I want the user to be able to click on any one of them at any time and then go to a related page. Problem is, evidently clicks stopped being listened for at some point if I use a loop to search through an array of clickable items. I thought having a function separate from the one that handles the animation would allow it to constantly listen no matter what the animated images were doing, but it seems once the "complete" function is called (for the "animate" function), the function that is listening for clicks (wholly separate from the animation, and using setInterval to listen for clicks) stops listening. Oddly enough, I believe I did not have this problem when just listening for "img" instead of an array of different images. Ideas as to what I'm doing wrong? More info needed? I tried to remove any irrelevant code below. var links = ["#portfolio", "#animations", "#games"]; $(function() { setInterval(function(){ for (var i=0; i<links.length; i++) { $(links[i]).click(function(){ window.location.replace("http://www.gog.com"); }); } }, 500); }); $(document).ready(function() { links.forEach(function(current){ //various vars var link = $(current); var footer3 = $(".footer3"); var over = true; var randomTime = 3000*(Math.random()+1); //dust vars ... //image vars var imageUrlShadow = 'images/home/non-char/shadow-pngs/shadow'; var imageUrlCharacter = 'images/home/char-pngs/'; var portfolioSrc; var animationsSrc; var gamesSrc; //animate the characters link.animate({ top: '0' }, { duration: randomTime, easing: 'easeOutBounce', step: function(now, tween) { /*handle shadows*/ ... /*handle characters*/ if (now < -25 && over == false) { ... } else if (now >= -25) { ... } $("#"+ link.data("portfolio")).attr('src', portfolioSrc); $("#"+ link.data("animations")).attr('src', animationsSrc); $("#"+ link.data("games")).attr('src', gamesSrc); /*handle dust*/ var dustDoneMoving = '-50px'; var dustNotMoving = '0px'; //if link is NOT touching footer3 ... //set to "sitting" images when animation is done complete: function() { ... setTimeout(function() { ... }, 1000); } }) }) });
var links = ["#portfolio", "#animations", "#games"]; $(function() { $(links.join(',')).click(function(){ window.location.replace("http://www.gog.com"); }); }); One time only and listener will be attached to the image.
Consider listening via window var links = ["#portfolio", "#animations", "#games"]; $(window).on('click', links.join(', '), function() { // do what you wanna });
I apologize, it turns out that, apparently, the problem was related to the z-index. There were some other divs with 0 opacity "covering up" the array divs. Setting the z-index to 2 for the array items has fixed the matter. Again, my apologies.
I have modified your code. See, if this works now. I have added one more array which contains some URLs. So, the intention is that on click of a particular element, its respective URL should be opened. So, the sequence in these array will matter with respect to each other. Also, I have made use of 'on', so that 'click' event should be handled even during animation. var links = ["#portfolio", "#animations", "#games"]; var sites = ["http://www.gog.com", "http://www.gog1.com", "http://www.gog2.com"]; //change these to the expected URLs $(function() { for (var i=0; i<links.length; i++) { $(document ).on("click",links[i],function(){ window.location.replace(sites[i]); }); } });