Prevent scrolling when done too quickly with jQuery - javascript

In order to prevent mousewheel scrolling to scroll the entire page when reaching the top/bottom of an element with its own scrollbars, I'm using Brandon Aaron's Mousewheel plugin.
This works fine, as long as I don't scroll too quickly. It seems that when scrolling really quickly, two events will pass the "I haven't reached the top/bottom" check yet and will both be executed. However, one of them will then scroll the element to the top/bottom and the next one will then scroll the entire page, which was what I was trying to prevent.
I'm currently doing this
$('.popupContents').bind('mousewheel', function (e, d) {
var t = $(this);
if (d > 0 && t.scrollTop() === 0) {
e.preventDefault();
} else if (d < 0 && (t.scrollTop() == t.get(0).scrollHeight - t.innerHeight())) {
e.preventDefault();
}
});
(As posted in Prevent scrolling of parent element? )
How do I make it so that the function properly stops all events at the top/bottom even when the user scrolls quickly?

I ended up manually tracking the desired scroll position and disallowing the normal scroll event altogether.
var wantedScrollTop = 0;
$('.popupBody').bind('mousewheel', function (e, d) {
var t = $(this);
var scrollTo;
if (d > 0) {
scrollTo = Math.max(0, wantedScrollTop - 30);
} else if (d < 0) {
scrollTo = Math.min(t.get(0).scrollHeight - t.innerHeight(), wantedScrollTop + 30);
}
if (typeof scrollTo !== "undefined") {
wantedScrollTop = scrollTo;
t.scrollTop(wantedScrollTop);
//t.stop().animate({ scrollTop: wantedScrollTop + 'px' }, 150);
}
e.preventDefault();
});
d is the scroll direction, so I'm manually keeping track of the wanted scroll position here. In my case there is only one popup at a time, so I didn't bother sticking the wantedScrollTop in a data- attribute or something similar on the element, which could be useful when youdo have multiple elements that need to track their own scroll position.
It is not doing a fluent scroll like your browser would, but it will change the vertical scroll position by 30 pixels for each time the scrollwheel triggers the event. I left the commented out line in the code to show how that could be achieved. However, for me this resulted in scrolling which feeled very lagged when scrolling quickly.

Related

How do i detect If I started scrolling from outside the div and entered the div or I am scrolling inside the div?

Issue right now: https://www.loom.com/share/c2567ccbd8e44ab49d1138e65ae77973
I have a section or div in the middle of the page. On every scroll, I need to detect if I entered that div after scrolling from outside that div (either up scroll or down scroll) or I am just scrolling inside that div?
I will explain what I am trying to achieve
This is the site https://dev.plusplus.co/events/
For this section https://prnt.sc/25nbxzq
What I am trying to achieve is when I start scrolling from the top of the page, and after I enter that above section, the section locks and there is a slick slider inside that div and after the section locks, I need to change slides on up and down scroll.
But What is happening right now is especially in firefox browser, If I scroll from the top and enter that div, the slider automatically changes to second. I need to lock the scroll first which works and when I enter that section and scroll and then only change slide to second on next scroll
Code I am using right now.
// debounce from underscore.js
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
// use x and y mousewheel event data to navigate flickity
function slick_handle_wheel_event(e, slick_instance, slick_is_animating) {
// do not trigger a slide change if another is being animated
if (!slick_is_animating) {
// pick the larger of the two delta magnitudes (x or y) to determine nav direction
var direction =
Math.abs(e.deltaX) > Math.abs(e.deltaY) ? e.deltaX : e.deltaY;
console.log("wheel scroll ", e.deltaX, e.deltaY, direction);
if (direction > 0) {
// next slide
slick_instance.slick("slickNext");
} else {
// prev slide
slick_instance.slick("slickPrev");
}
}
}
// debounce the wheel event handling since trackpads can have a lot of inertia
var slick_handle_wheel_event_debounced = debounce(
slick_handle_wheel_event
, 80, true
);
// slider #2
const slick_3 = $("#firstscrollsection .content-left");
slick_3.not('.slick-initialized').slick({
dots: false,
vertical: true,
speed: 400,
fade: true,
waitForAnimate: false,
verticalSwiping: true,
slidesToShow: 1,
arrows: false,
infinite: false,
});
var slick_3_is_animating = false;
slick_3.on("afterChange", function(index) {
console.log("Slide after change " + index);
slick_3_is_animating = false;
});
slick_3.on("beforeChange", function(index) {
console.log("Slide before change " + index);
slick_3_is_animating = true;
});
$("#firstscrollsection .section-wrapper-animated").on("wheel", function(e) {
slick_handle_wheel_event_debounced(e.originalEvent, slick_3, slick_3_is_animating);
});
Assuming you're trying to create a parallax effect, I believe you're going about it the wrong way.
I'm also curious about these lines of code:
// pick the larger of the two delta magnitudes (x or y) to determine nav direction
var direction = Math.abs(e.deltaX) > Math.abs(e.deltaY) ? e.deltaX : e.deltaY;
console.log("wheel scroll ", e.deltaX, e.deltaY, direction);
Is there a reason for comparing deltaY with deltaX? As I understand that you're calculating scroll direction on the vertical axis.
MDN Web Docs outlines the following — maybe this could be the source of the issue with Firefox:
Note: Don't confuse the wheel event with the scroll event. The default action of a wheel event is implementation-specific, and doesn't necessarily dispatch a scroll event. Even when it does, the delta* values in the wheel event don't necessarily reflect the content's scrolling direction. Therefore, do not rely on the wheel event's delta* properties to get the scrolling direction. Instead, detect value changes of scrollLeft and scrollTop of the target in the scroll event.
If the pointer isn't directly hovering over the section, its wheel event won't fire. Also, the wheel event won't consider touch or scrollbar events. It's a better idea, in my opinion, to instead listen for the scroll event on the documentElement.
Here is my suggestion for changing the slide on the next scroll. You can detect if the scrollTop of the document meets your target element's(section in this case) offsetTop, then you will start calculating scroll direction before navigating the slider.

How to make jquery setinterval movement in mobile smoother?

Sorry for bad english.
Here's link to site.
My task is to create site with no scroll. When user clicks to right part of screen car starts to move forward. When it reaches middle of screen - car stops and fixed content-block starts to move in opossite direction. if user moves cursor to left side of screen (while holding mouse button clicked) car should move backward.
Desktop version works as expected. But mobile version is slow. (it's not exactly slow, it's not as smooth as desktop i guess)
What can i do fix this problem?
On touchstart event i get event.pageX value to check what part of screen user touched. (so i would know what direction car should move) and store this value in variable "mousePos". Then i call setInterval with movement function
On touchend event i clear interval to stop car from moving.
On touchmove i will rewrite "mousePos" var with new event.pageX value. For example: user clicked, car starts to move, if user moved cursor to left i will use this var to check direction and turn car back.
In mouseMove function i will check car position and decide what action should be done - either move car or move background and i'll check if it reached start of end points
events:
$(document).on('mousedown touchstart', '.mouse-click', function(event){
clicking = true;
mousePos = event.pageX;
if ( event.target.className == 'helper' ) {
showModal();
} else {
moveStuff = setInterval(mouseMove, 1);
}
});
$(document).on('mouseup touchend', function(){
clicking = false;
carLights.removeClass('blink');
clearInterval(moveStuff);
})
$(document).on('mousemove touchmove', '.mouse-click', function(event){
if(clicking == false) {
return;
} else {
mousePos = event.pageX;
}
});
function:
function mouseMove() {
let offset = parseInt( mainContent.css('left') );
let offsetCar = parseInt( car.css('left') );
if ( mousePos > middleScreen ) {
carLights.removeClass('blink');
if ( offset < - ( contentWidth ) ) {
return;
} else {
rotateWheelsForward();
if ( offsetCar < middleScreen ) {
car.css('left', (offsetCar + mouseSpeed) + 'px');
} else {
mainContent.css('left', (offset - mouseSpeed) + 'px');
}
}
} else if ( mousePos < middleScreen ) {
carLights.addClass('blink');
if ( offset > 0 ) {
carLights.removeClass('blink');
return;
} else {
rotateWheelsBackward();
if ( offsetCar > minCarLeft ) {
car.css('left', (offsetCar - mouseSpeed) + 'px');
} else {
mainContent.css('left', (offset + mouseSpeed) + 'px');
}
}
}
}
So how can i make movement smoother in mobile? (i use iphone 5s safari, but tested in iphone 6, still works bad)
What changes should i implement to this code?
I suggest to use transform rather than position eg: left, top cause thats really affect layer reflow-repaint. Use requestAnimationFrame (wisely) to perform smooth event animation like scroll, mouseup, or any other event.
Then use will-change: transform; to element which will "transformed" on the future. This will creates new layer and prepare for the element changes later.
In my case, relative position impact reflow or green flash on the rendering tool chrome. So I prefer use fixed/absolute position to prevent this.
Here's some great article for you to get Leaner, Meaner, Faster Animations with requestAnimationFrame and how to achieving 60 fps animations with css
Hope this help ;)

Custom buttons to replace browser scrolls -- hold and continuous scroll?

I have custom buttons that replaces the browser scrollbar. The idea is so that scrolling oversize elements in a page wouldn't result to a dozen scroll bar on a page.
See: https://jsfiddle.net/bwgxs6ng/
Since I must show some code sample (according to some SO error message), see this:
$('.right').on('click', function(event) {
var target = $(".image-container");
var current_x = target.scrollLeft();
if( target.length ) {
event.preventDefault();
$(target).animate({
scrollLeft: current_x+100
}, 500);
}
});
It's very simple, basically it takes current scroll position of the parent, and add x to it based on the direction that's clicked.
However, going further, I want it to imitate the hold and continuous scroll, but I'm not sure how to do it.
1) What is the mouse hold event called? (OK, this part is answered, it's called MouseDown as someone point out of the duplicate)
2) What is the continuous scrolling called, and how can I do something that'd imitate the browser's continuous scroll?
You can just call .animate() repeatedly (with easing set to linear, for smooth movement) inside your setInterval() callback. Just arrange for the interval to be equal to the animation duration, so that the next animation starts just when the previous one ends.
Or, better yet, make the interval shorter (say, 50 ms or less) and just call .prop() instead of .animate(), effectively performing your own animation. (This is how jQuery implements animation internally, anyway.)
Anyway, here's how I'd rewrite your code to support smooth continuous scrolling:
var speed_x = 0, speed_y = 0;
var timer = null;
var target = $(".image-container");
function scroll() {
if (speed_x == 0 && speed_y == 0) return;
var current_x = target.scrollLeft();
var current_y = target.scrollTop();
target.prop({
scrollLeft: current_x - speed_x,
scrollTop: current_y - speed_y
});
}
$('.control').on('mouseover mouseout', function (event) {
var $this = $(this);
var speed = (event.type == 'mouseover' ? 10 : 0)
if ($this.hasClass('left')) speed_x = +speed;
if ($this.hasClass('right')) speed_x = -speed;
if ($this.hasClass('up')) speed_y = +speed;
if ($this.hasClass('down')) speed_y = -speed;
}).on( 'mousedown', function () {
scroll();
if (timer !== null) clearInterval(timer);
timer = setInterval(scroll, 50);
return false;
});
$(document).on('mouseup', function () {
if (timer !== null) clearInterval(timer);
timer = null;
});
Note how the animation is started and stopped in the mousedown and mouseup handlers, but the direction of movement is set on mouseover and mouseout. This allows you to change the scrolling direction while holding the mouse down, by dragging the cursor from one edge to another.
(For bonus points, add divs with e.g. class="control up left" in the corners of the scroll area, so that holding the mouse down over those corners will allow you to scroll diagonally. The JS code above already supports it.)
you need to set an interval on mousedown, and clear the interval on mouseup, as done in this fiddle for left and right.
The relevant code change is that we removed the click event and replaced it with
$('.left').on('mousedown', function(event) {
... scroll code ...
interval = setInterval(function(){
... scroll code ...
},500);
})
.on('mouseup',function(){clearInterval(interval);});

how to ignore lots of scroll-events due to smooth scrolling of browser?

i'm trying to implement such a feature:
webpage has two columns. narrow left one - with menu and some info, and wide right one - with the main content of page. height of the left column is much smaller than the right's one
so when user scrolls down the webpage and the left narrow column is already above the viewport, it gets hidden giving extra width for right column.
after i hide the left column i should scroll browser's window little bit upper in order to corresponding element on the right gets placed on the top of viewport (it jumps upper when i hide the left col, because the width of right col gets increased)
i've implemented this, but the problem is in smooth scrolling in all modern browsers. when you press down key or page down or mousewheel or use your finger in touch-devices, browser generates a lot of scroll-events during one scroll.
it looks like this:
scrollTop == 500
scrollTop == 520
scrollTop == 530
scrollTop == 535
scrollTop == 537
and the moment when my script realises that left col should get hidden corresponds to scrollTop of 500, and at this very moment my script tries to set scroll top to the new value, but it gets instantly overridden by following browser smoothscroll events:
scrollTop == 500 //browser
scrollTop == 450 //mine! i need to save this position!
scrollTop == 520 //browser
scrollTop == 530 //browser
scrollTop == 535 //browser
scrollTop == 537 //browser
so how can i cancel all changes of scrollTop below?
this is my code if needed:
$(function() {
//height of left column
window.main_left_height1 = $('#main_left_div').height();
var ar = $.grep($('.page_container > div'), function(item) {
return $(item).position().top >= window.main_left_height1;
});
//anchor element on the right which placed on the same height with the end of left col
window.main_left_anchor = ar[0];
//we will toggle left column on this scroll height
window.main_left_height1 = $(window.main_left_anchor).position().top;
window.main_left_state = true;
window.ignore_scroll = false;
window.ignore_scroll_value = -1;
});
if (is_mobile || true)
{
$(window).scroll(function(e) {
var scroll_top = $(this).scrollTop();
//i've tried to ignore browser scrolling after i set its value manually in the script but this doesn't work
if (window.ignore_scroll)
{
if (Math.abs(window.ignore_scroll_value - scroll_top) < 50)
{
window.ignore_scroll = false;
window.ignore_scroll_value = -1;
return true;
}
e.preventDefault();
return false;
}
if (window.main_left_state)
{
if (window.main_left_height1 && scroll_top >= window.main_left_height1)
{
$('#main_left').hide();
window.main_left_state = false;
if (!window.main_left_height2)
window.main_left_height2 = $(window.main_left_anchor).position().top;
window.ignore_scroll = true;
window.ignore_scroll_value = window.main_left_height2;
$(this).scrollTop(window.main_left_height2);
}
}
else
{
if (window.main_left_height2 && scroll_top < window.main_left_height2)
{
$('#main_left').show();
window.main_left_state = true;
window.ignore_scroll = true;
window.ignore_scroll_value = window.main_left_height1;
$(this).scrollTop(window.main_left_height1);
}
}
});
}
any suggestions? thanks!
As I see since browser got input (like mouse scroll or key press) and scroll event is generated you can do nothing with it. If events are generated window will be scrolled and if even there will be page rearrangement scroll offset wouldn't be changed. It means you will have jumpy scrolling if you will change scrollTop value after.
Try to change your right column position (Top) and not scrollTop. For example, when user scrolls down and you calculated that scrollTop need to be changed for -60px, just add those 60px to your right panel's top (you can even animate it). I think it might help.
And the second suggestion is to handle mouse and keyboard input by your self to generate scrolling, like it is done here: How to disable scrolling temporarily? (NIGHTMARE! I think :))

Best way of making draggable element jump to cursor (and start drag) when mousedown on parent

I've got a home-made slider made from jQuery UI's draggable() function:
$("#petrolGauge .fuelBar .slider").draggable({
containment: "parent",
axis: "x",
drag:function(){
updValues();
},
start:function(){
$(this).css("background-color","#666");
},
stop:function(){
//checkForm();
$(this).css("background-color","#AAA");
}
});
This is for the following markup:
<div id="petrolGauge">
<input id="endPet" name="endPet" type="hidden" value="0">
How much fuel was left in the tank when you were finished? (Use the slider) <b>(~<span class="petLeft">0</span>%)</b>
<span class="mandatory">*</span><br />
<div class="fuelBar">
<div title="Drag" class="slider"></div>
</div>
This works a treat, when I click on the slider. But I'd like it so that when I click the fuel bar (the slider's parent) the slider not only starts dragging but also jumps to the cursor. I've achieved it by doing this:
$("#petrolGauge .fuelBar").on("mousedown",function(e){
slider = $("#petrolGauge .fuelBar .slider");
left = e.pageX-($(this).offset().left)-(slider.width()/2);
updValues();
slider.css("left",left).trigger(e);
});
Two problems with this:
Firstly, when clicking on the parent I get a couple of second's delay before the slider starts to drag? I've tried and tested this in Chrome and IE and both do it. Secondly if the cursor is less than half of the slider's width away from the edge of the parent, the slider will move to the outside of the parent. Wouldn't be hard to fix this with a couple of checking, but was wondering if there was another way? I'm suprised that draggable() doesn't have any parameters for this to be honest. I didn't want to use slider() if I could help it but if it's the only way, then it's the only way.
Here's a fiddle to work with.
The reason you get the delay is because you use .trigger() inside the .on() event which creates a big loop. As a result the loop slows down the moving process.
$("#petrolGauge .fuelBar").click(function (e) { // use click instead of mousedown
slider = $("#petrolGauge .fuelBar .slider");
left = e.pageX - ($(this).offset().left) - (slider.width() / 2);
if(left > 570) { left = 570; } else if(left < 0) { left = 0; }
// it looks like a draggable bug due to the manual position change, so use a small check
slider.css("left", left); // change the position first
updValues(); // then calculate and update the div
// no need to trigger the event a second time because it will loop until jQuery exceeds it's trigger limit.
});
Here's an updated FIDDLE
Updated answer
To make .slider move accordingly to the mouse movement when not directly dragged, bind a mousemove event to the mousedown and unbind it when mouseup. Then in .mousemove() you change the position of .slider.
var move = function (e) {
left = e.pageX - ($('#petrolGauge .fuelBar').offset().left) - (slider.width() / 2);
if (left > 570) {
left = 570;
} else if (left < 0) {
left = 0;
}
slider.css("left", left);
updValues();
};
var slider = $("#petrolGauge .fuelBar .slider");
$("#petrolGauge .fuelBar").mousedown(function (e) {
e.preventDefault();
left = e.pageX - ($(this).offset().left) - (slider.width() / 2);
if (left > 570) {
left = 570;
} else if (left < 0) {
left = 0;
}
slider.css("left", left)
$(this).bind('mousemove', move);
updValues();
}).mouseup(function () {
$(this).unbind('mousemove');
});

Categories