Custom scroll UX - javascript

I'm currently working on a project where the desired user experience involves a very customized interaction with scroll events.
Problem to solve:
The page has X sections, each of them with a height equal to the viewport hight height: 100vh;. When a user scrolls down on the viewport, the current visible section stays where it is and a scroll indicator animates based on a threshold of distance scrolled (30px, for example). Once the user has scrolled the threshold, the next section comes up from the bottom of the screen and covers up the current section (which still doesn't move).
Initial Approach:
Set each section to an absolute position and adjust them with by changing CSS classes based on the scrollwheel event. Body overflow:hidden and transform property to manipulate the sections. I am running in to issues, though.
The scrollwheel event seems to be documented as very unstable solution to implement.
The .wheelDelta aspect of the event fires asynchronously and is difficult to capture a gesture with. (On chrome, it just spits out a multiple of 3 a bunch of times rather than a distance of the gesture in px). This makes it difficult to set a threshold and animate the elements that are responsive to that threshold.
I basically want to be able to track the number of pixels a scrollwheel-like event is covering and apply that number to the position of a certain scroll-hint element until the scroll threshold is met. Once it is met, a function fires to change the classes and update some information on the page. If the threshold is not met, the scroll hint element goes back to it's default position.
My attached approach doesn't feel very conducive to accomplishing this, so I'm looking for either 1) a different and more stable approach or 2) revisions / criticisms on what I'm doing wrong with this approach to make it stable.
(function scrollerTest($){
$('body').on ('mousewheel', function (e) {
var delta = e.originalEvent.wheelDelta,
currentScreenID = $('section.active').data('self').section_id,
currentScreen = $('section.part-' + currentScreenID),
nextScreenID = currentScreenID + 1,
nextScreen = $('section.part-' + nextScreenID);;
if (delta < 0) { // User is Scrolling Down
currentScreen.removeClass('active').addClass('top');
nextScreen.removeClass('bottom').addClass('active')
} else if (delta > 0) { // User is Scrolling Up
}
});
}(jQuery));
body {
overflow: hidden;
padding: 0;
margin: 0;
}
section {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100vh;
z-index: 999;
background-color: #CA5D44;
transition: 0.8s all ease-in-out;
}
section.part-1 {
position: relative;
z-index: 9;
}
section.part-2 {
background-color: #222629;
}
section.active {
transform: translateY(0);
}
section.top {
transform: translateY(-10%);
}
section.bottom {
transform: translateY(100%);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<body>
<section class="part-1 home active" data-self='{ "section_id" : 1, "section_title" : "Home", "menu_main_clr" : "#fff" , "menu_second_clr" : "#CA5D44", "logo_clr" : "white" }'>
</section>
<section class="part-2 about bottom" data-self='{ "section_id" : 1, "section_title" : "About", "menu_main_clr" : "#CA5D44" , "menu_second_clr" : "#fff", "logo_clr" : "white" }'>
</section>
<section class="part-3 contact bottom" data-self='{ "section_id" : 1, "section_title" : "Contact", "menu_main_clr" : "#fff" , "menu_second_clr" : "#CA5D44", "logo_clr" : "white" }'>
</section>
</body>
Edit Note:
The snippet seems to have some issue with firing the event and changing classes after the first instance - not sure why. On my local example it fires them all at once..
**Edite Note 2: **
Listed code is just a copy of the closest behaviour I could achieve. The whole threshold functionality seems pretty unattainable with this method, unfortunately, as the wheel event doesn't behave like a scroll event.

the whole scroll topic is rather complex especially when you think about touch scroll events, too.
There are some libraries out there - see this list for example: http://ninodezign.com/30-jquery-plugins-for-scrolling-effects-with-css-animation/
I used https://projects.lukehaas.me/scrollify/ before and it might allow you to do what you intend (using the before callback eventually) but I can't tell without trying myself. Also regard that scrollify is relatively big (8kb minified) in comparison to other libraries.

You can approach this using by ensuring each content-filled section is followed by a blank transparent gap (in the example below, also 100vh in height) and then using javascript to apply position:fixed to each content-filled section when it hits the top of the viewport.
Example:
var screens = document.getElementsByClassName('screen');
function checkPosition() {
for (var i = 0; i < (screens.length - 1); i++) {
var topPosition = screens[i].getBoundingClientRect().top;
if (topPosition < 1) {
screens[i].style.top = '0';
screens[i].style.position = 'fixed';
}
}
}
window.addEventListener('scroll',checkPosition,false);
.screen {
position: absolute;
display: block;
left: 0;
width: 100%;
height: 100vh;
}
.red {
position: fixed;
top: 0;
background-color: rgb(255,0,0);
}
.orange {
top: 200vh;
background-color: rgb(255,140,0);
}
.yellow {
top: 400vh;
background-color: rgb(255,255,0);
}
.green {
top: 600vh;
background-color: rgb(0,191,0);
}
.blue {
top: 800vh;
background-color: rgb(0,0,127);
}
p {
font-size: 20vh;
line-height: 20vh;
text-align: center;
color: rgba(255,255,255,0.4);
}
<div class="red screen">
<p>Screen One</p>
</div>
<div class="orange screen">
<p>Screen Two</p>
</div>
<div class="yellow screen">
<p>Screen Three</p>
</div>
<div class="green screen">
<p>Screen Four</p>
</div>
<div class="blue screen">
<p>Screen Five</p>
</div>

Related

Why adding an invisible uninteractive overlay increases performance of scroll synchronization? [Chrome]

I need to synchronise the scroll of two elements: A content area and a "header".
But when I used the scroll event to change the position of the other element with a CSS transform, there was a notable delay between the user scrolling and the header moving, which is distracting, specially on Chrome.
However, when comparing to other applications that use the same method for synchronising the scroll position, they didn't have it. After many hours trying to find why, I've finally found it: they have an invisible overlay that covers the whole scroll area, which is also not interactive (CSS pointer-events: none)
I've prepared a playground below, where the overlay is visible and doesn't cover the whole area so it can be compared easily.
At least this behaviour is happening in my machine, a Mac with Chrome, and I scroll through the trackpad gesture. There also needs to be some load, so this snippet also adds a 30ms blocking loop every 100ms to simulate one
const content = document.getElementById("content");
const header = document.getElementById("header");
// Add content
const helloes = new Array(100).fill("Hello!").join("<br /><br />");
header.innerHTML = content.innerHTML = helloes;
// Synchronize scroll of content to header by adding `transform`
content.addEventListener("scroll", (evt) => {
header.style.transform = `translateY(${-content.scrollTop}px)`;
});
// Simulates performance hit, either low-end computer or a component doing extra load
setInterval(() => {
let start = Date.now();
while (Date.now() < start + 30);
}, 120);
.main {
position: relative;
display: flex;
}
.main>div {
overflow: auto;
height: 200px;
padding: 10px;
border: 1px solid black;
border-radius: 5px;
white-space: nowrap;
}
#content {
padding-right: 200px;
}
#overlay {
position: absolute;
left: 160px;
top: 50px;
width: 100px;
height: 100px;
box-sizing: border-box;
background: rgba(0, 0, 0, 0.3);
pointer-events: none;
}
<div class="main">
<div>
<div id="header">
</div>
</div>
<div id="content">
</div>
<div id="overlay"></div>
</div>
As it can be quite tricky to reproduce, I've recorded this behaviour in this image
It's quite subtle, but as you can see, when I'm scrolling with the mouse over the scroll area, there's a visible lag. If I'm scrolling instead on top of the uninteractive overlay, the scroll synchronization is perfect.
However, I totally can't understand why this happens - What is the reason adding an overlay and scrolling with the mouse on top of it improves the performance?

Horizontal parallax effect to text on scroll

So I sort of got it working. I believe I am not understanding the javascript correctly.
I took this from another thread, however it isn't behaving quite the way I am trying to achieve. I see the variables are a math equation that bases the movement on the window height.
How can I manipulate the equation so that I can control "Some cool text."'s initial position (if you notice on load it takes the correct position, and then on scroll it gets moved by JS) to stay where I want it?
What controls the speed and intensity of the movement and how can I manipulate that?
I believe I am just not understanding the syntax that controls all these variables, can you point me in the right direction for some reading to understand these specific variables? Thank you. :D
https://jsfiddle.net/codingcrafter/kv9od1ju/22/
/* Custom Horizontal Scrolling Parallax */
.hero-two {
overflow: hidden;
position: relative;
min-height: 500px;
}
h1 {
-webkit-text-stroke-width: 0.1rem;
-webkit-text-stroke-color: black;
color: #fff;
font-family: 'Open Sans', Helvetica, Times New Roman !important;
font-weight: 900;
}
.para-ele {
position: absolute;
width: 100%;
font-size: 5rem;
}
#hero-first {
left: 75%;
top: 15%;
}
#hero-second {
left: -32%;
bottom: 10%;
}
.container {
position: relative;
box-sizing: border-box;
width: 100%;
height: 100%;
}
<div class="container">
<div class="hero-two">
<h1 id="hero-first" class="h1 para-ele">
Some cool text.
</h1>
<h1 id="hero-second" class="h1 para-ele">
Some boring text.
</h1>
</div>
</div>
$(document).ready(function() {
var $horizontal = $('#hero-first');
$(window).scroll(function() {
var s = $(this).scrollTop(),
d = $(document).height(),
c = $(this).height();
scrollPercent = (s / (d - c));
var position = (scrollPercent * ($(document).width() - $horizontal.width()));
$horizontal.css({
'left': position
});
});
});
So you want to move the text from left to right or right to left?
I have done something similar to your issue but I used jQuery to handle the scroll effect.
If you are going to use the code below you will need to wrap the text within a element with the class Introduction
As the page scrolls the element will append the styles dynamically to the element.
<h1 class="introduction">
WE ARE A <br><span class="d">DIGITAL</span><br>PARTNER
</h1>
$(window).scroll(function() {
var wScroll = $(this).scrollTop();
$(".introduction").css({
transform: "translateX(-" + wScroll / 23 + "%)"
})
});
Demo: https://guide-nancy-64871.netlify.com/
When page is scrolled the header text moves to the left.
Read more on css transform: https://css-tricks.com/almanac/properties/t/transform/
Hope this helps!

How do I prevent scroll back up with JavaScript or jQuery?

I have a webpage where there is a full height intro image. Underneath this image is the main body of the site with a regular site header at the top, I'm trying to create an effect where once the user scrolls down to the site header, they cannot scroll back up to view the intro image.
CSS Classes:
Main Intro Image: .cq-fullscreen-intro
Site Header: .nav-down
I had a poke around on StackOverflow but I can't find anything that addresses this circumstance, can anyone point me in the right direction to achieve this using jQuery?
you can use JQuery scrollTop function like this
$(function() {
$(window).scroll(function() {
var scroll = $(window).scrollTop();
// set the height in pixels
if (scroll >= 200) {
// after the scroll is greater than height then you can remove it or hide it
$(".intro-image").hide();
}
});
});
So instead of scrolling, I personally think it would be better to have it be actionable. Forcing the user to manually do the transition (and all in between states) is a bad idea. If the user scrolls half way, and see's something actionable (menu, button, input field) is it usable? If it is, what happens if they submit... very awkward. If it isn't usable, how do they know when it is? How do they know it's because they haven't scrolled all the way. It's very poor user experience.
In the following example, I've created a pseudo-screenport for you to see what's actually going on. The .body container in your real site would be the body element.
Code Pen Example
$(document).ready(function(){
$('.splash-screen').on('click', function(){
$('.splash-screen').addClass("is-hidden");
});
})
html, body{
background: #eee;
margin: 0;
padding: 0;
}
.flex-root {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
width: 100vw;
}
.web-container {
width: 640px;
height: 480px;
background: #fff;
}
.body {
font-size: 0; // this is only to prevent spacing between img placholders
position: relative;
}
.splash-screen{
position: absolute;
transition: transform 1s ease-in-out;
}
.splash-screen .fa {
position: absolute;
z-index: 1;
font-size: 24px;
color: #fff;
left: 50%;
bottom: 15px;
}
.splash-screen.is-hidden {
transform: translateY(-110%);
transition: transform 1s ease-in-out;
}
<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="flex-root">
<div class="web-container">
<div class="body">
<div class="splash-screen">
<img src="https://via.placeholder.com/640x480?text=Splash+Screen"/>
<i class="fa fa-chevron-circle-up"></i>
</div>
<img src="https://via.placeholder.com/640x60/cbcbcb?text=Menu"/>
<img src="https://via.placeholder.com/640x420/dddddd?text=Site Body"/>
<div>
</div>
</div>
While its not direclty preventing you from scrolling up and its not jQuery, I would suggest to remove/hide the element once its out of view.
You could get the current scroll position, relative to the top of the page, and check if its greater than the elements height:
const target = document.getElementById('my-target')
const targetHeight = target.getBoundingClientRect().height
const scrollEventListener = () => {
if (
document.body.scrollTop > targetHeight ||
document.documentElement.scrollTop > targetHeight
) {
target.remove()
window.removeEventListener('scroll', scrollEventListener)
document.documentElement.scrollTop = 0
}
}
window.addEventListener('scroll', scrollEventListener)
Here is a codepen https://codepen.io/bluebrown/full/aboagov

Be able to scroll within underlying element while cursor is over position absolute element in front of it

How can I be able to scroll article while having my mouse cursor over .header while still having .header clickable? If I set z-index: -1 to .header I'm able to scroll while having the cursor over .header, but it's no longer clickable.
Demo
Fiddle
HTML:
<div class="row">
<div class="off-canvas-wrap">
<div class="inner-wrap">
<div class="header">
I should be clickable
</div>
<article class="small-12 columns">
<div style="height:5000px">
</div>
</article>
</div>
</div>
</div>
CSS:
article {
overflow-y: auto;
}
article,
body,
html,
.off-canvas-wrap,
.off-canvas-wrap .inner-wrap,
.row {
height: 100%;
width: 100%;
}
.header {
position: absolute;
width: 400px;
height: 400px;
background: #000;
left: 50%;
top: 50%;
margin-left: -200px;
margin-top: -200px;
z-index: 1;
color: #fff;
text-align: center;
padding-top: 1em;
}
If you want a CSS solution, there is none — this is because of how mouse events are directly related to the visibility of the item to the pointer/cursor: e.g. if you place .header in the back such that it is not accessible (so that scroll events on article can be triggered), it will not register a click event, too.
A JS-based solution would be listening to the mousewheel() event (with this plugin, available as a CDN-hosted plugin, too) and then manually triggering scrolling on the article element. However, this does not replicate the default scrolling behavior on individual OSes, and may appear choppy on OSes that has smoothed scrolling events (like OS X).
Without further ado:
// Cache article's position from top (might change if the page is loaded with a hash, so we cannot declare it as 0)
var fromTop = $('article').scrollTop();
$('.header').mousewheel(function(e,d) {
// Prevent default scrolling behavior, even when .header is overflowing
e.preventDefault();
// Trigger scroll in window
// You can change how much to amplify the 'd', which is the delta (distance registered from the scrollwheel). I have chosen it to multiply it by 10
fromTop = fromTop - d*10;
$(this).next('article').scrollTop(fromTop);
}).click(function() {
// Just testing
alert('Header is clicked on!');
});
Here is the proof-of-concept JSFiddle: http://jsfiddle.net/teddyrised/CK7z8/2/
Warning: In the event that there are multiple .header elements targeting multiple article elements on the same page, you will have to iterate through each .header-article pair and cache the fromTop separately for each pair.

Side-Scrolling img div using .scrollWidth

I have simple html document that contains divs which hold a series of images:
<div id="container">
<div id="imagelist">
<a href="images/1.jpg"><img src="images/1b.jpg"/>
<a href="images/2.jpg"><img src="images/2b.jpg"/>
<a href="images/3.jpg"><img src="images/3b.jpg"/>
<a href="images/4.jpg"><img src="images/4b.jpg"/>
<a href="images/5.jpg"><img src="images/5b.jpg"/>
<a href="images/6.jpg"><img src="images/6b.jpg"/>
</div>
</div>
I would like to be able to scroll horizontall through the images when hovering over the left or right edge of the div (I have multiple #imagelists all stacked vertically)
I'm trying to use the .scrollWidth() function as such (this is in my script.js file):
var imglist = $('#imagelist');
$(imglist).mousemove(function(e) {
var percent = e.clientX / $(imglist).width();
$(imglist).scrollWidth($(imglist).width() * percent);
});
This doesn't work at all, of course! I've been trying to model this after some good examples I've seen, such as This. What should I alter to make my #imagelist scrollable?
Here's a way to do it using offset and relative positioning.
demo
The HTML looks similar to yours, with the exception that we create elements for the edges. The benifit is that we can style them with CSS, should you ever decide you want :hover styles (example in the demo).
<div class="imagecontainer">
<div class="imagelist">
<img src="http://placehold.it/400x300">
...
<img src="http://placehold.it/400x300">
</div>
<div class="edge right"></div>
<div class="edge left"></div>
</div>
The entire CSS is in the demo, this is just the essentials.
.imagecontainer {
width: 100%;
height: 400px;
overflow-x: hidden;
position: relative;
}
.imagelist {
/* Width allows up to 100 screenfuls, feel free to add a 0
Limiting can be done in the JavaScript */
width: 10000%;
height: 400px;
position: relative;
/* Give it a default left of negative to allow scrolling in either direction */
left: -500px; top: 0;
clear: right;
}
.imagelist img {
float: left;
}
.edge {
position: absolute; top: 0;
width: 50px; height: 100%;
}
.edge.left { left: 0; }
.edge.right { right: 0; }
The JavaScript is the fun part. We find the edges and watch for hover and leave events. Considering only one may be hovered at once (both practically and due to mouseenter), we simply have one timer pointer. This timer controls our animation, and is used to stop the animation (clearInterval) on mouseleave. 20 times per second we move the .imagelist 5 pixels in one direction. That's determined based on which edge we're hovering over.
Instead of using $('.imagelist') we use .parent().find('.imagelist') so that there may be any number of image lists on the page.
var timer = 0;
$('.edge').mouseenter(function(){
var $self = $(this);
var $imglist = $self.parent().find('.imagelist');
timer = setInterval(function(){
var amount, changed;
if ($self.hasClass("left"))
amount = -5;
else
amount = 5;
changed = $imglist.offset().left + amount;
$imglist.offset({left: changed});
}, 50)
}).mouseleave(function(){
clearInterval(timer);
});
It's a little rough, but you can polish it up to suit your needs.

Categories