I've tried searching to see if there's already a question about this but can't find anything - so apologies if this is in fact a duplicate!
I've seen on some websites a feature where, when scrolling, the scroll stop point is forced to stop at a specific element rather than just wherever the user actually stopped scrolling.
I imagine this can be achieved via jQuery, but can't seem to find any documentation or help articles about it.
So, here's some example HTML...
<div id="one" class="block"></div>
<div id="two" class="block"></div>
<div id="three" class="block"></div>
With this as the CSS...
#one {
background: red;
}
#two {
background: green;
}
#three {
background: yellow;
}
.block {
width: 200px;
height: 100vh;
}
And what I'm looking to achieve is that when the user scrolls their browser from div 'one' to div 'two', once they've started scrolling over div 'two' and they then stop scrolling the browser automatically jumps them so that they see div 'two' in full, rather than a bit of the bottom of div 'one' and then most of div 'two' - I've definitely seen it done before but no idea how!
I hope this makes sense, and thanks in advance for any help or insight anyone can offer...
I don't remember too well, but I guess there are many ways to achieve what you want. One thing that came to my mind is to wrap around your divs and make a separate hidden div with full height. I did this adhoc solution below:
Once scroll approaches a threshold, I move to the div I should be looking at and vice versa. Here is a working solution FIDDLE:
HTML
<div id="phantom"></div>
<div id="wrapper">
<div id="one" class="block"></div>
<div id="two" class="block"></div>
<div id="three" class="block"></div>
</div>
CSS
#one {
background: red;
}
#two {
background: green;
}
#three {
background: yellow;
}
.block {
width: 200px;
height: 100vh;
}
#wrapper {
overflow:hidden;
position:fixed;
top:0px;
}
#phantom {
visibility:hidden;
}
JS
!function(){
//the array of divs
var divs = Array.prototype.slice.call(document.getElementsByClassName("block")), count = divs.length,
wrapper = document.getElementById("wrapper"),
phantom = document.getElementById("phantom"),
//the speed of scroll
scrollStep = 5,
//total length of phantom div
totalLength = Array.prototype.slice.call(wrapper.children).reduce(function(ac,d,i,a){return ac += d.clientHeight},0),
//store the animation frame here
currentFrame;
//wrapper is overflow hidden
wrapper.style.height = totalLength/count + "px";
//phantom has full height
phantom.style.height = totalLength + "px";
//add listener for scroll
window.addEventListener("scroll",function(){
//throttle the function
if(this._busy){return};
this._busy = true;
var that = this;
window.requestAnimationFrame(function(){
that._busy = false;
var heightOfDocument = Math.max(document.documentElement.scrollHeight,document.body.scrollHeight),
totalScroll = Math.max(document.body.scrollTop,document.documentElement.scrollTop),
//which element should we look at?
whichElement = Math.round(totalScroll/heightOfDocument*count);
//if we are already around, don't do anything
if(divs[whichElement]._current){
return;
} else {
//cancel the last animation if any and start a new one
window.cancelAnimationFrame(currentFrame);
divs.forEach(function(d,i){delete d._current});
moveTo(divs[whichElement]);
}
});
},false);
//helper function to linearly move to elements
function moveTo(node){
node._current = true;
var top = node.offsetTop,
current = node.parentNode.scrollTop,
distance = top - current,
step = distance < 0 ? -scrollStep : scrollStep;
if(Math.abs(distance) < scrollStep){
node.parentNode.scrollTop = top;
return;
} else {
node.parentNode.scrollTop += step;
}
//store the current frame
currentFrame = window.requestAnimationFrame(function(){
moveTo(node);
});
}
}();
You obviously need to attach 'resize' event to update the values of the totalLength and set the correct new length on wrapper and phantom. You can also implement a easing function to modify the scrolling behavior to your taste. I leave them to you as homework.
Related
I have the following scenario:
I have an API which returns multiple div's. And in my UI, I have one parent div in which i need to show these div's. The condition is, if child div is overflowing parent, I need to show them on next page.
For eg: lets say my API is returning string like this:
<div class="ab0"></div>
<div class="ab1"></div>
<div class="ab2"></div>
<div class="ab3"></div>
<div class="ab4"></div>
and the parent div can fit in only ab0, ab1, ab2. Then I want to show these 3 div's 1st and when user click on '>' symbol I need to show ab3, ab4. Also if ab2 is partially overflowing and if I can show only overflowing part on next page, that will be great.
Is there any way I can do this.
Thanks in advance
A simple suggestion is to use the system's scroll functionality.
The system 'knows' how much it can show at once and as long as you can find out the height of the parent div you can move up and down the children (or part children if an exact number don't fit into the parent at once) using Javascript scrollTop.
const parent = document.querySelector('.parent');
const h = parent.offsetHeight;
let page = 0;
const lastPage = Math.floor((parent.scrollHeight + 1) / h);
function next() {
if (page < lastPage) {
page++;
parent.scrollTop = parent.scrollTop + h;
}
}
function prev() {
if (page > 0) {
page--;
parent.scrollTop = parent.scrollTop - h;
}
}
.parent {
height: 64px;
overflow: auto;
}
.parent div {
height: 2em;
border: 1px solid;
position: relative;
}
button {
font-size: 2em;
}
<div class="parent">
<div class="ab0">0</div>
<div class="ab1">1</div>
<div class="ab2">2</div>
<div class="ab3">3</div>
<div class="ab4">4</div>
</div>
<button onclick="prev();"><</button>
<button onclick="next();">></button>
</button>
I've made a timeline using a sort of following this: https://www.w3schools.com/howto/howto_css_timeline.asp and set it to position: sticky. This is in a container called, div.timeContainer. Next to it, there's some text in a separate div. The idea is that the user scrolls down, reading the text on the right, while the timeline/overview on the left is in view.
The problem right now is that if I set the height of div.timeContainer, resizing the window means that the timeline will stop being in view/sticky around half-way through since the div on the right has become longer.
This (and variations) is what I have tried so far:
const historyContainer = document.querySelector("div.history").style.height
document.querySelector("div.timeContainer").style.height = historyContainer
I have prepared for you a simple example of assigning parent height to a child. An example in vanilla js.
let parent_div = document.querySelector('.parent');
let child_div = document.querySelector('.child');
let click_button = document.querySelector('input');
click_button.onclick = function(){
child_div.style.height = parent_div.offsetHeight + 'px';
};
.parent {
width: 100%;
height: 300px;
background-color: yellow;
}
.child {
width: 50%;
background-color: green;
}
<input type="button" value="click me to get the height of the child div">
<div class="parent">
<div class="child">
</div>
</div>
I have a fixed div containing an image that scrolls with the user from the top of the page. As new content divs enter the viewport I want the image to change.
I found a related piece of code that will change the image based on how far a user scrolls in pixels. This works, but only if the viewport is a specific size, else the image changes too early/late:
Example
I'm trying to modify this so that the change is instead based on when another div comes into view so that it works no matter the screen size (content div heights are set with relative units). I think this can be done if the other divs positions are saved to a variable and then used in place of the pixel values in the above code. However I can't seem to get this right, probably because I've not calculated the other div positions correctly.
$("#display1").fadeIn(1000);
$(window).scroll(function() {
var pos = $(window).scrollTop();
var first = $("#first").offset();
var second = $("#second").offset();
if (pos < first) {
hideAll("display1");
$("#display1").fadeIn(1000);
}
if (pos > first && pos < second) {
hideAll("display2");
$("#display2").fadeIn(1000);
}
etc...
});
function hideAll(exceptMe) {
$(".displayImg").each(function(i) {
if ($(this).attr("id") == exceptMe) return;
$(this).fadeOut();
});
}
You should try
getBoundingClientRect()
JS method, since It gets the position of the elements relative to the viewport. Check this answer: https://stackoverflow.com/a/7557433/4312515
Here is a quick proof of concept of changing a background image based on an element getting into view.
There are three divs. When the third div reaches the bottom of the viewport it will change the color of the background. When the third divs scroll out of the view again the background color is reset to its initial color.
Normally you should debounce the scroll event to prevent slowing down the UI. For this example I didn't debounce the event so you get a better sense of when the background is changed.
const
card3 = document.getElementById('card3'),
background = document.getElementById('background');
let
isCardVisible = false;
function checkDivPosition() {
const
cardTopPosition = card3.getBoundingClientRect().top,
viewportHeight = document.documentElement.clientHeight,
isInView = cardTopPosition - viewportHeight < 0;
if (isInView && !isCardVisible) {
background.style.backgroundColor = 'rebeccapurple';
isCardVisible = true;
} else if (!isInView && isCardVisible) {
background.style.backgroundColor = 'orange';
isCardVisible = false;
}
}
function onWindowScroll(event) {
checkDivPosition();
}
window.addEventListener('scroll', onWindowScroll);
body {
margin: 0;
}
.background {
height: 100vh;
opacity: .2;
position: fixed;
transition: background-color .3s ease-out;
width: 100vw;
}
.card {
border: 1px solid;
height: 100vh;
width: 100vw;
}
.card + .card {
margin-top: 5vh;
}
<div id="background" class="background" style="background-color:orange"></div>
<div class="card">
Card 1
</div>
<div class="card">
Card 2
</div>
<div id="card3" class="card">
Card 3.
</div>
I have a script that has a div with a width larger than its' parent, with the parent being set to overflow: hidden;. I have javascript that is setting the left positioning of the big div to create "pages". You can click a link to move between pages.
All of that works great, but the problem is if you tab from one "page" element to another, it completely messes up all the left positioning to move between the pages.
You can recreate this bug in the fiddle I set up by setting your focus to one of the input boxes on page ONE and tabbing until it takes you to page two.
I've set up a demo here.
The code that is important is as follows:
HTML:
<div class="form">
<div class="pagesContainer">
<div class="page" class="active">
<h2>Page One</h2>
[... Page 1 Content here...]
</div>
<div class="page">
<h2>Page Two</h2>
[... Page Content here...]
</div>
</div>
CSS:
.form {
width: 400px;
position: relative;
overflow: hidden;
border: 1px solid #000;
float: left;
}
.pagesContainer {
position: relative; /*Width set to 10,000 px in js
}
.form .page {
width: 400px;
float: left;
}
JS:
slidePage: function(page, direction, currentPage) {
if (direction == 'next') {
var animationDirection = '-=';
if (page.index() >= this.numPages) {
return false;
}
}
else if (direction == 'previous') {
var animationDirection = '+=';
if (page.index() < 0) {
return false;
}
}
//Get page height
var height = page.height();
this.heightElement.animate({
height: height
}, 600);
//Clear active page
this.page.removeClass('active');
this.page.eq(page.index()).addClass('active');
//Locate the exact page to skip to
var slideWidth = page.outerWidth(true) * this.difference(this.currentPage.index(), page.index());
this.container.animate({
left: animationDirection + slideWidth
}, 600);
this.currentPage = page;
}
The primary problem is that whatever happens when you tab from say, an input box on page one to something on page 2, it takes you there, but css still considers you to be at left: 0px;. I've been looking all over for a solution but so far all google has revealed to me is how to stop scrollbar scrolling.
Any help or suggestions would be appreciated, thanks!
P.S. The html was set up like this so that if javascript is disabled it will still show up all on one page and still function properly.
I updated your fiddle with a fix for the first tab with the form: http://jsfiddle.net/E7u9X/1/
. Basically, what you can do is to focus on the first "tabbable" element in a tab after the last one gets blurred, like so:
$('.form input').last().blur(function(){
$('.form input').first().focus();
});
(This is just an example, the first active element could be any other element)
Elements with overflow: hidden still have scrolling, just no scroll bars. This can be useful at times and annoying at others. This is why your position left is at zero, but your view of the element has changed. Set scrollLeft to zero when you change "pages", should do the trick.
So, here it is:
I'll have 4 divs. Example below. Each div a particular height (around 1500px) but have a width of 100%. Each div is a different colour.
I want it so that when the user scrolls the page and reach a particular point, javascript will kick in and automatically scroll the user to the next div.
So, say the user is vertically scrolling and div #2 is appear and div #1 is disappearing. When div #1 has about 200px left, the page will automatically scroll down so that div #2 is flush with the top of the browser window.
A good example: http://thejuly16.com/ Which basically does it but can't work out how.
1
Content here
2
Content here
3
Content here
4
Content here
That page isn't doing anything for me :/
Anyway, if I get what you mean, you should have some anchors on top of every div, hook some code to the scroll event, check scrollTop() value on it, and scroll to the anchors when this value is in a desired range. You can check this fiddle and the relevant jQuery code:
$(window).bind('scroll', function(){
if (($(window).scrollTop() > 1300) && ($(window).scrollTop() < 1350)) {
window.scrollTo(0,1500);
}
});
This might be a strange behavior for the user, since scrolling up is pretty messed up. However, we can fix this by checking if the user is going up or down in the page, like in this fiddle, just checking if the last scroll position was higher or lower than the current scroll position:
var currentScroll = 0;
var previousScroll = 0;
$(window).bind('scroll', function(){
currentScroll = $(window).scrollTop();
if (($(window).scrollTop() > 1300) && ($(window).scrollTop() < 1350) && currentScroll > previousScroll) {
window.scrollTo(0,1500);
}
previousScroll = $(window).scrollTop();
});
Obviously, you'd need to add as many if statements as "jumps" you want in your page.
I have a solution as given in the code below. Somehow its not working on jsFiddle but working on my machine. Please try it in your own editor
<HTML>
<HEAD>
<SCRIPT LANGUAGE="JavaScript">
var isWorking = false;
var lastScrollPosition
function adjust(oDiv) {
if(oDiv.scrollTop > lastScrollPosition && !isWorking && oDiv.scrollTop % 400 > 300) {
isWorking = true
scroll(oDiv);
} else
lastScrollPosition = oDiv.scrollTop;
}
function scroll(div) {
if(div.scrollTop % 400 > 10) {
div.scrollTop = div.scrollTop + 10;
lastScrollPosition = div.scrollTop;
setTimeout(function(){scroll(div);}, 10);
} else
isWorking = false;
}
</SCRIPT>
</HEAD>
<BODY>
<div style="height: 440px; border: solid 1px red; overflow-Y: auto" onscroll="adjust(this)">
<div style="height: 400px; border: solid 1px green"></div>
<div style="height: 400px; border: solid 1px green"></div>
<div style="height: 400px; border: solid 1px green"></div>
<div style="height: 400px; border: solid 1px green"></div>
<div style="height: 100px"></div>
</div>
</BODY>
</HTML>
I think this functionality is available with jQuery. I have tried this but I was doing this on OnClick event in Javascript. In your case, onFocus or any other suitable event like mouseover etc should work.
Hope this helps.