non-linear scrolling - javascript

Scrolling s is like, well, linear:
s(x) = x with x among [0, ∞]
I'd like to apply a more fancy function, say x^2:
but I'd don't really know if it's possible and how...
I'd like to know your thougts on this.
EDIT
For example: is it possible to change the scrollTop value while scrolling?
Cheers.

A high level approach to your problem:
Capture scroll events, keep track of the time you got the last one
Compute actual velocity vA based on time to last event
vA(dT):
// if we last scrolled a long time ago, pretend it was MinTime
// MinTime is the dT which, when scrolled
// at or less than, behaves linearly
if (dT > MinTime) dT = MinTime
vA = MinTime / dT
Devise some transformation to perform on vA to get desired velocity vD:
vD(vA):
// quadratic relationship
vD = vA * vA
Calculate a "scroll factor" fS, the ratio of vD to vA:
fS(vD, vA):
// this step can be merged with the previous one
fS = vD / vA
Calculate the delta scroll dS using fS and dSi, the initial scroll size (1 scroll event's worth of scrolling)
dS(fS):
dS = fS * dSi
Scroll by that much
Scroll(dS)
If you scroll less than once per MinTime or slower, you will get typical linear behavior. If you try to scroll faster, you will scroll quadratically with your actual scroll speed.
I have no idea how to actually do this with javascript, but I hope it provides somewhere to start.
Is there a unit of scrolling I can use by any chance? My terminology looks funny.

This should be helpful for capturing mouse wheel 'speed':
$(document).on('DOMMouseScroll mousewheel', wheel);
function wheel (event) {
var delta = 0;
if (event.originalEvent.wheelDelta) {
delta = event.originalEvent.wheelDelta/120;
} else if (event.originalEvent.detail) {
delta = -event.originalEvent.detail/3;
}
if (delta) {
handle(delta, event.currentTarget);
}
if (event.preventDefault) {
event.preventDefault();
}
event.returnValue = false;
}
function handle (delta, target) {
// scrollYourPageDynamiclyWithDelta(delta*delta);
// manipulate of scrollTop here ;)
}

So this is more conceptual, but I think using the functions that others mentioned to detect scroll speed and such this could be helpful.
Logic:
Disable defaults for scrolling on a div.
Add a second div that just detects mouse wheel speed using #Tamlyn's code. Perhaps you could put this div behind your working div or wrap it around or inside your content some how. I'd try to just put it on the side for now.
Next, scroll the div based on the input from this 2nd div. Use your custom scrolling function to change scroll speed based and direction of the scroll. There will be some "devil in the details moments" here probably.

Related

Mobile Like Time Picker For React Web

I am trying to make an Alarm clock UI using react and I am stuck at this component where users can scroll or swipe through hours, minutes etc.. I tried some methods but failed.
I tried on scroll, on wheel, but my problem is I just can't get the accurate value which user sees(like the 03:30 PM).
I don't want you to help me with code, just need to know how to approach this.
I would approach this problem like this
let's address the challenges here
Scrolling to the desired points only
Looped scrolling
Auto pick the Value - without clicking basically.
Scrolling to the desired points only
This concept is called snap scrolling, you can use some library for that avoid writing its logic by yourself because then you will end up handling a lots of edge cases.
Looped scrolling
You need to handle this using basic JS logic you can provide some extra buffer elements at the end and at the starting refer to this example
https://codepen.io/lemmin/pen/bqNBpK
window.onscroll = function () {
// Horizontal Scroll.
var y = document.body.getBoundingClientRect().top;
page.scrollLeft = -y;
// Looping Scroll.
var diff = window.scrollY - dummy_x;
if (diff > 0) {
window.scrollTo(0, diff);
}
else if (window.scrollY == 0) {
window.scrollTo(0, dummy_x);
}
}
Auto pick the Value
one very basic approach could be, you can get the scroll offset on change of scroll, and as you have the height of every entity you can get the element that is in focus(or highlighted to the user).
Below is one more approach, you can create a selector div then you can check for the overlap on-scroll-stop whichever element is within this div, you can get its value.
personally, I think the first approach will be simpler and more stable. I have seen people using both types of approaches.
let me know if could help with anything else.

Three.js examples - how to approach them

I've been learning Three.js and I came across these examples by Nat Geo. I was wondering if anyone knows how to make the scroll wheel control the progression of the animation and how it is mixed with their website's regular look... It seems like in the first example that the webgl element is in the background with the normal html elements floating over it, but I don't understand how they timed the animations to it.
If anyone has insight on how these were created I'd appreciate it!
https://www.nationalgeographic.com/science/2017/09/cassini-saturn-nasa-3d-grand-tour/#enceladus
https://www.nationalgeographic.com/science/2016/11/exploring-mars-map-panorama-pictures/
Scrolling is a browser event.
I did not look at the linked pages specifically, instead I'll describe a more generic scenario.
Once you understand how the event works, it's easy to see that one could track how far one has scrolled on the current page. Look at the scroll distance as a kind of trigger, where once you pass distance X, then something specific should happen.
Consider the below snippet, where crossing certain thresholds changes the background color of the button:
var button = document.getElementById("myButton");
var position = 0;
function scrollHandler(e) {
e.preventDefault();
if (e.wheelDeltaY > 0 || position !== 0) {
position += e.wheelDeltaY;
}
console.log(position);
if (position < 500) {
button.style.background = "";
}
if (position > 500) {
button.style.background = "red";
}
if (position > 1000) {
button.style.background = "green";
}
}
button.addEventListener("mousewheel", scrollHandler);
button.addEventListener("DOMMouseScroll", scrollHandler);
<input id="myButton" type="button" value="BUTTON!" />
Run the code snippet, then hover over the button and scroll up/down.
For animations, the time is what you need.
var uniTime = gl.getUniformLocation( prg, "uniTime" );
gl.uniform1f( uniTime, time );
But you can cheat and use the amount of scrolling in place of real time.
For instance, if you need that the full scroll of your screen represents 7 seconds, you can use this bit of code in your animation loop:
time = 7000 * screen.scrollTop / screen.scrollHeight;
Here is a living example: https://jsfiddle.net/m1a9qry6/23/

Scroll by pushing side of screen with JavaScript

Windows 8 has this neat feature where you scroll through your apps by "pushing" the side of the screen.
I want to know if anyone has any ideas to accomplish this in JavaScript.
Essentially, the screen does should NOT scroll if you hover over the side of the screen, but should rather be able to detect when the user is attempting to go beyond the viewport and cannot.
Is such a thing possible?
Sure, you just need to figure out their algorithm if you want to duplicate it.
You can track the last several known locations of the pointer to determine velocity and direction and stop the scrolling as soon as the direction changes, for example.
I'm using something along the lines of:
$(window).mousemove(function (e) {
if (getIsPageEdge()) {
if (lastX == e.pageX) {
console.debug('pushing the page');
}
var now = new Date().getTime();
if (lastUpdate == null || now - lastUpdate > 500) {
lastUpdate = now;
lastX = e.pageX;
}
}
});
Essentially, onmousemove, if the cursor is at the edge of the viewport, and the X value is not changing (with a time delay added to compensate for the event processing delay), then change the scroll position of the containing div.

Hiding a DIV based on screen height with debouncing

I'm working on a page with a simple side nav that has a fixed position, set top: 30%. What I need to happen is at the bottom of the page I want to fade the menu out so it doesn't overlap the footer, the code below works but I think it is checking on scroll so much it takes to long to calculate when you scroll down fast.
Is there a faster/more lightweight way to calculate when to hide the side-nav? I'm not familiar with debouncing but would it help?
Elements:
.body-container-wrapper - total height of page
.footer-container-wrapper - total height of the footer that we want the nav to be hidden at
.internal-side-nav - the menu position: fixed, top: 30%, right: 0
Example Page: http://hsb1.hubspot.com/profile-page-template
Script:
<script type="text/javascript">
$(document).scroll(function () {
var y = $(this).scrollTop();
if (y < $('.body-container-wrapper').outerHeight() - $('.footer-container- wrapper').outerHeight() - 400 ) {
$('.internal-side-nav').fadeIn();
} else {
$('.internal-side-nav').fadeOut();
}
});
</script>
I hadn't heard of debounce so I had to look it up. It could potentially help, but that would be an extra plugin you'd have to include and maintain and it might not work exactly how you want (I didn't see anything indicating that it does "bunches or time frames", just seemed to be bunches, which means it might fire late on you).
Instead, what you could do is throttle it yourself with a little bit of timing.
var scrollTimeInterval = 200; // how often we allow the action to trigger, in ms.
var lastScrollTime = 0;
var scrollTimeoutId = null;
$(document).scroll(function () {
var now = (new Date()).getTime();
var dScrollTime = now - lastScrollTime;
lastScrollTime = now;
if (dScrollTime < scrollTimeInterval) {
// Set a timeout so we catch the last one.
scrollTimeoutId = setTimeout(function() { $(document).scroll(); }, scrollTimeInterval - dScrollTime);
return; // too soon, so we'll skip
}
// Clear any potentially pending timeout.
clearTimeout(scrollTimeoutId);
var y = $(this).scrollTop();
if (y < $('.body-container-wrapper').outerHeight() - $('.footer-container- wrapper').outerHeight() - 400 ) {
$('.internal-side-nav').fadeIn();
} else {
$('.internal-side-nav').fadeOut();
}
});
With this, the scroll event simply won't do anything if it hasn't been a certain amount of time since it last triggered. To ensure we catch the last scroll event (the last one before they stopped scrolling), I added a timeout which will trigger the scroll event one last time. We also have to make sure to clear that if we handle another scroll event before it fires (so we don't double up on it).
You can control the time interval it allows with the first variable. If 200ms feels a bit sluggish, you can reduce it to 100ms or 50ms and see if that gives a better balance.
Hope that helps.
Dealing with scroll events, in certain circumstances, there's not much you can do.
Solution 1: setTimeout that cancels itself on each iteration
This method is most efficient I believe, but maybe not ideal for you, because there will still be 300ms in which the sidenav would visually overlap the footer before it fades out.
var scrollTimeout = false;
$(document).scroll(function(){
clearTimeout(scrollTimeout);
var _this = this; // for the setTimeout function to know what "this" is
scrollTimeout = setTimeout(function(){
// your original code block goes here
// BUT, replace "this" with "_this"
}, 300);
});
The above essentially only runs your code when the user has not scrolled for at least 300 milliseconds.
Solution 2: Just good old optimization (not too many tricks)
This solution should hide the sidenav immediately, but still runs on every scroll, just does less
var myScrollEvent = (function(){
var $win = $(window);
var $foot = $('.footer-container-wrapper');
var footer_top = $foot.offset().top; // where on the page the footer begins
var $nav = $('.internal-side-nav');
var nav_height = $nav.height(); // maybe use outerHeight(true)?...who knows, only you
var is_hidden = false;
// this is the actual function we want to run on-scroll
return function(){
// jquery, even on fixed elements, still seems to account for scroll position
// when calculating top offset value, below is the coordinate of the bottom of the nav
var nav_bottom = $nav.offset().top + nav_height;
// if the bottom coord of the nav is lower than the top coord of the footer
if(nav_bottom > footer_top && !is_hidden){
is_hidden = true;
// hide it code
}else if(is_hidden){
is_hidden = false;
// show it code
}
};
})();
$(window).scroll(myScrollEvent);
The idea here is to cache some variables and also do the calculation a slightly different way. Your way doesn't seem by any means wrong, but this is just an alternative. Note that with this method, we're assuming the nav height will never change.
You could always combine the two solutions if you'd like as well.
Also, note that I haven't done any browser-2-browser testing, so if there are any flaws, of course let me know.

unit of increment in scrollable areas / divs in javascript?

in javascript can I make sure that my large div scroll vertically
only in chunks of (let's say) 16 pixels
In java, those are called 'units of increment'.
I can't find anything similar in javascript:
I want to ensure that a certain area (div) when partially scrolled is always a multiple of 16 the view.
That allows me to do tricks with background images and others.
thanks
var lastScroll = 0;
$('div').scroll(function(){
var el = $(this),
scroll = el.scrollTop(),
round = lastScroll < scroll ? Math.ceil : Math.floor;
lastScroll = round(scroll/16) * 16;
el.scrollTop(lastScroll);
});
http://jsfiddle.net/m9DQR/2/
Ensures scrolls are done in multiples of 16 pixels. You can easily extend this to be a plugin that allows for a variable amount (not a fixed, magical 16).
Yes, this is possible, but it will require using javascript to capture the scroll event and then manipulate it. This script (sorry jQuery is what I had) and overrides the scroll event. It then replaces it with the exact same scroll distance. You could perform your own math to adjust the value of scrollTo. We have to check both mousewheel and DOMMouseScroll events because the first is not supported by FF. This doesn't seem to apply in your case, but a user may have the number of lines to scroll set to something other than the default three. So the if statement calculates the distance. I left it in there though in case other people stumble on this question and it is important to them though.
$('body').bind('mousewheel DOMMouseScroll', function(e) {
var scrollTo = null;
if (e.type == 'mousewheel') {
scrollTo = (e.wheelDelta * -1);
}
else if (e.type == 'DOMMouseScroll') {
scrollTo = 40 * e.detail;
}
//adjust value of scrollTo here if you like.
scrollTo = 16;
if (scrollTo) {
e.preventDefault();
$(this).scrollTop(scrollTo + $(this).scrollTop());
}
});
Coming from another programming language I also found JavaScript difficult when dealing with UI. In your case I would just set a handler to the event onscroll and query the position of the div relative to the scroll position. Return false whenever position of div is not divisible by 16px and create a counter to allow reposition after another 16px is scrolled.

Categories