Calculating % of the element that's been scrolled - javascript

I have a set of divs. Lets for this example believe they are like this.
<body>
<div id="ignore" data-tag="sections" style="height:20%">
</div>
<div id="2" data-tag="sections" style="height:20%">
Content content content
</div>
<div id="3" data-tag="sections" style="height:20%">
Content content content
</div>
<div id="4" data-tag="sections" style="height:20%">
Content content content
</div>
<div id="5" data-tag="sections" style="height:20%">
Content content content
</div>
<div id="end" data-tag="sections" style="height:0%">
</div>
</body>
I want to know which element the user is viewing. Viewing is considered in view when the div hits the top of the page.
I also want to know how far they have scrolled through the current element in relation to the next sibling (as a percentage).
As an added bonus I would like to know how much there is left to scroll of the whole page excluding the top "ignore" div.
Can anyone direct me in the right direction?
This is what I've got but 1) it fires WAY to many times 2) after the first element it fails.
My brain hurts.
function get_init_distances(){
array=[]
var elems = document.querySelectorAll('[data-tag="sections"]')
elems.forEach(function(el){
array.push({el:el,top:el.getBoundingClientRect().top})
})
return array
}
function get_current_el_and_perc(){
var i = 0
var start = 0
var perc = 0
var old_id = ""
//loop elements
array.forEach(function(el){
if(i !== 0){
//get perc of completion of first window
perc = (el.top - (start + window.pageYOffset)) / el.top
//log
if(perc > 0 && perc < 1){
console.log(perc)
console.log(old_id)
return
}
}
//set old id and top
old_id = el.el.id
start = start + el.top
i ++
})
}
var scrolls
window.onload = () => {
scrolls = get_init_distances()
document.addEventListener("scroll",get_current_el_and_perc)
}

Related

I am looking to have an image in navbar change to a different when a corresponding section on the page is in viewport

I have images in a navbar that change their src when the image hovered over using mouseover. I am looking to have that same hover effect on the image when a section is scrolled to in viewport that matches the id.
This is what I have attempted recently. The hover works fine, but the image doesn't seem to change when section is scrolled to.
One example in the navbar
<div class="number"><img class="ai" src="https://unsplash.com/photos/Z1wosLgwGT8" onmouseover="this.src='https://unsplash.com/photos/q98u_gIRs6I'" onmouseout="this.src='https://unsplash.com/photos/Z1wosLgwGT8'" alt="3"></div>
One div example with the id to match navbar link
<div class="test" id="1">
<div class="test" id="2">
<div class="test" id="3">
<div class="test" id="4">
The JS I have so far
const testDiv = document.querySelector("#3");
const aiImg = document.querySelector(".ai");
window.addEventListener("scroll", function() {
const viewportHeight = window.innerHeight;
const divTop = testDiv.getBoundingClientRect().top;
const divBottom = testDiv.getBoundingClientRect().bottom;
if (divTop <= viewportHeight && divBottom >= 0) {
aiImg.src = "https://unsplash.com/photos/q98u_gIRs6I";
}
});

Scroll child div while parent div is position sticky in vue JS

I have a use case for my landing page, where when user enters one of the <section> of the page, the section becomes fixed but the elements inside that <div> starts scrolling. Once the inner div is done scrolling the parent <section> should start scrolling as a normal section and should go away to the top with a scroll.
I've created a similar structure in the jsFiddle below. But not able to achieve the desired feature.
https://jsfiddle.net/8qm67ks9/2/
I wanted to achieve this behavior as presented on this website. Section SS below
HTML
... Other sections before
<section ref="stickyDiv" class="scroller py-sm-128">
<div class="">
<div ref="wrapper" #scroll="scrollerListener" class="wrapper-box">
<div class="d-flex px-sm-128">
<h3 class="section-heading">Access to all the exclusive opportunities</h3>
<div class="col-sm-6">
<img class="img-fluid"
src="https://res.cloudinary.com/stack-finance/image/upload/v1663728853/app-assets/v2/mask_group_590_e5gbgr.png"
>
</div>
</div>
<div class="d-flex px-sm-128">
<h3 class="section-heading">Access to all the exclusive opportunities</h3>
<div class="col-sm-6">
<img class="img-fluid"
src="https://res.cloudinary.com/stack-finance/image/upload/v1663728853/app-assets/v2/mask_group_590_e5gbgr.png"
>
</div>
</div>
<div class="d-flex px-sm-128">
<h3 class="section-heading">Access to all the exclusive opportunities</h3>
<div class="col-sm-6">
<img class="img-fluid"
src="https://res.cloudinary.com/stack-finance/image/upload/v1663728853/app-assets/v2/mask_group_590_e5gbgr.png"
>
</div>
</div>
</div>
</div>
</section>
... Other sections in the bottom
Vuejs
methods: {
...
listenBodyScroll(e) {
if (this.isMobile) {
return;
}
const stickyDiv = this.$refs.stickyDiv;
const wrapper = this.$refs.wrapper;
const dim = stickyDiv.getBoundingClientRect();
console.log(dim.y, dim.height, '---scrollTop');
if (dim.y <= 0) {
if (Math.ceil(dim.height) <= Math.abs(dim.y)) {
stickyDiv.style.position = 'relative';
stickyDiv.style.top = 'auto';
stickyDiv.style.height = 'auto';
wrapper.style.overflowY = 'hidden';
wrapper.scrollTop = wrapper.scrollHeight;
return;
}
wrapper.focus();
stickyDiv.style.position = 'sticky';
stickyDiv.style.top = '100px';
stickyDiv.style.height = '100vh';
wrapper.style.overflowY = 'auto';
}
},
scrollerListener({ target: { scrollTop, offsetHeight, scrollHeight, style } }) {
if (this.isMobile) {
return;
}
const stickyDiv = this.$refs.stickyDiv;
if ((Math.ceil(scrollTop) + offsetHeight) >= scrollHeight) {
stickyDiv.style.position = 'relative';
stickyDiv.style.top = 'auto';
stickyDiv.style.height = 'auto';
style.overflowY = 'hidden';
console.log('bottom!');
}
}
}
Vue direction v-scroll
Vue.directive('scroll', {
inserted(el, binding) {
const f = function(evt) {
if (binding.value(evt, el)) {
window.removeEventListener('scroll', f);
}
};
window.addEventListener('scroll', f);
}
});
There are no secret techniques in web development. Use your browser's inspect tool and check the website's HTML/CSS and see exactly how it works for yourself.
I did this myself and took some of the website's HTML and CSS and recreated a simple version you can see in this codesandbox
Basically, there are two divs of 50% width side by side that exist within the same parent div forcing them to be the same (very large) height. Once you scroll the parent div into view, the image container with position: sticky keeps it in the same relative screen position until you get to the bottom of its parent div where it will then scroll away with the rest of the parent div content.

How to count the number of divs shown in each row when it depends on the screen resolution

I have the following code:
HTML
<div class="content" style="height: 30px;">
<div class="content-box" id="myOptions">
<div class="options-holder">
<input type="checkbox" name="11111" id="Bob_111">
<label for="111">Alex_1</label>
</div>
<div class="options-holder">
<input type="checkbox" name="22222" id="Bob_222">
<label for="222">Alex_2</label>
</div>
<div class="options-holder">
<input type="checkbox" name="33333" id="Bob_333">
<label for="333">Alex_3</label>
</div>
<div class="options-holder">
<input type="checkbox" name="44444" id="Bob_444">
<label for="444">Alex_4</label>
</div>
<div class="options-holder">
<input type="checkbox" name="55555" id="Bob_555">
<label for="555">Alex_5</label>
</div>
<div class="options-holder">
<input type="checkbox" name="66666" id="Bob_666">
<label for="666">Alex_6</label>
</div>
<div class="options-holder">
<input type="checkbox" name="77777" id="Bob_777">
<label for="777">Alex_7</label>
</div>
<div class="options-holder">
<input type="checkbox" name="88888" id="Bob_888">
<label for="888">Alex_8</label>
</div>
</div>
</div>
<div class="button-wrapper"> <a href="#" class="toggle-trigger" id="showMoreButton" style="display:none;">
<span data-collapse-text="Show less" data-expand-text="Show more" class="state up">Show more</span></a>
</div>
JAVASCRIPT
$.fn.myToggle = function () {
return this.each(function () {
var targetContainer = $(this),
targetBox = targetContainer.find('.content'),
targetTrigger = targetContainer.find('.toggle-trigger'),
targetState = targetTrigger.find('.state'),
contentBox = targetBox.find('.content-box'),
boxHeight = contentBox.outerHeight(),
optionHeight = targetBox.find('.options-holder').outerHeight();
$(window).on('resize', function () {
boxHeight = contentBox.outerHeight();
if (targetState.hasClass('down')) {
targetBox.stop(true, false).animate({
height: boxHeight
});
}
});
targetTrigger.on('tap', function () {
targetBox.stop(true, false);
if (targetState.hasClass('down')) {
targetState.text(targetState.data('expand-text'));
targetBox.animate({
height: optionHeight
});
} else {
targetState.text(targetState.data('collapse-text'));
targetBox.animate({
height: boxHeight
});
}
targetState.toggleClass('up down');
return false;
});
});
};
$(this.el).myToggle().on('click', '.checkbox-toggle', function (event) {
var toggle = $(this),
container = toggle.closest('.option-filter');
event.preventDefault();
container.find(':checkbox').prop('checked', toggle.hasClass('all'));
});
The problem is that the .option-holder divs don't fit in only one row in my .content-box div so i have to hide them and create a show more/less toggle button to show or hide the rest.
Everything works fine until the point that I only have the specific amount of .option-holder divs to fit only one line so i don't need the toggle button (the amount of divs comes dynamically from a server).
My current solution is to count the number of divs and show the toggle button only if they are more than 4 (in most screen resolutions i get 4 divs per row).
The problem is when the screen resolution is bigger and I get 5 or 6 per row.
If I have 6 divs per row but only 5 divs to show then the button still exists because I show it after 4 divs.
I know there are plenty easy fixes but I am not allowed to rewrite the code and change its logic so I have to find a way to count how many divs are shown each time in a row.
The code now works just by changing the height on div .content every time I click the button in order to show or hide the rest divs without giving the "not showing" divs any extra attributes e.g. style="display: none; to work with.
Any suggestions??
var divs = document.querySelectorAll('.options-holder'); //or getElementsByClassName... or just $('.options-holder').....
var x = divs.length;
var number_of_elements_in_first_row = 0;
for ( var i = 0; i < x; i++;) {
if ( divs[0].offsetHeight !== divs[i].offsetHeight ) {
divs[i].style.display = "none"; // hide divs[i]
/* or if you need only number of elements per row
add this instead hiding elements */
number_of_elements_in_first_row = i;
break;
}
}
if ( divs[0].offsetHeight !== divs[x].offsetHeight )
// add/show your MORE button... your function call
So, main idea is, if you have more than one row, last element will have different vertical position.
EDITED:
This code will go trough array of targeted divs and hide all divs that are not vertically aligned with the first one. This is not complete solution, more of idea how to handle this problem.
var divs = $('.options-holder');
for(var key in divs){
if (key > 0){
if (divs[0].offsetLeft == divs[key].offsetLeft) {
$("#showMoreButton").css("display", "block");
}
}
}
This is the solution needed as .offsetLeft is the same in every first div in each row..

javascript - random div on page load?

Hoping someone can help as I do not know much about JS
I have 3 divs
<div id="content1">This is content 1 </div>
<div id="content2">This is content 2 </div>
<div id="content2">This is content 2 </div>
I require some JS that randomly loads one of those divs on page load and hide the other two
Any help would be much appreciated
Thanks
You can select all div elements when the page loads, then pick a random one to keep and hide the rest.
var elems = $("div");
if (elems.length) {
var keep = Math.floor(Math.random() * elems.length);
for (var i = 0; i < elems.length; ++i) {
if (i !== keep) {
$(elems[i]).hide();
}
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="content1">This is content 1 </div>
<div id="content2">This is content 2 </div>
<div id="content3">This is content 3 </div>

Goto Next Anchor Button

I have set up some anchors and a little menu up top. when I click a menu item, it will scroll to that anchor.
what I want to do is have a next arrow on the menu to determine the next anchor on my page and scroll to it onClick.
My anchors are #header, #box1 - #box5
I would like to do it with JavaScript if possible.
here is my page
My Page
There is an HTML collection called document.anchors. To go to the next anchor, get the current anchor name from the URL and look for it in document.anchors. If you find it, the next one will be the next index. If you're at the last index, set the anchor to the first. Otherwise, if there is no match, just set it to the first.
This allows you to use any scheme for naming anchors, they will be visited in the order they appear in the DOM.
e.g.
<head>
<!-- Hide script-dependent content -->
<style type="text/css">
.requiresScript-block, .requiresScript-inLine {
display: none;
}
div.spacer {
height: 500px;
border: 1px solid blue;
}
</style>
<script type="text/javascript">
function goToNextAnchor() {
var anchors = document.anchors;
var loc = window.location.href.replace(/#.*/,'');
var nextAnchorName;
// Get name of the current anchor from the hash
// if there is one
var anchorName = window.location.hash.replace(/#/,'');
// If there is an anchor name...
if (anchorName) {
// Find current element in anchor list, then
// get next anchor name, or if at last anchor, set to first
for (var i=0, iLen=anchors.length; i<iLen; i++) {
if (anchors[i].name == anchorName) {
nextAnchorName = anchors[++i % iLen].name;
break;
}
}
}
// If there was no anchorName or no match,
// set nextAnchorName to first anchor name
if (!nextAnchorName) {
nextAnchorName = anchors[0].name;
}
// Go to new URL
window.location.href = loc + '#' + nextAnchorName;
}
// Display script-dependent content if javascript available
document.write(
'\u003Cstyle type="text/css"\u003e' +
'.requiresScript-block {display: block;}' +
'.requiresScript-inLine {display: inline;}' +
'\u003C/style\u003e'
);
</script>
</head>
<body>
<ol>
<li>Go to header
<li>Go to box 1
<li>Go to box 2
<li>Go to box 3
<li>Go to box 4
<li>Go to box 5
</ol>
<!-- Only shown if javascript available -->
<button class="requiresScript-inLine" onclick="goToNextAnchor()">Next</button>
<a name="header"></a><h1>Header</h1>
<div class="spacer">content</div>
<p><a name="box1"></a><p>Box 1</p>
<div class="spacer">content</div>
<p><a name="box2"></a><p>Box 2</p>
<div class="spacer">content</div>
<p><a name="box3"></a><p>Box 3 </p>
<div class="spacer">content</div>
<p><a name="box4"></a><p>Box 4</p>
<div class="spacer">content</div>
<p><a name="box5"></a><p>Box 5</p>
</body>
Something using an onClick in your HTML:
===>
...and then the JavaScript:
var max = 5;
function goToNext() {
var hash = String(document.location.hash);
if (hash && hash.indexOf(/box/)) {
var newh = Number(hash.replace("#box",""));
(newh > max-1) ? newh = 0 : void(null);
document.location.hash = "#box" + String(newh+1);
} else {
document.location.hash = "box1";
}
}
Change max the highest number you want to go (for box1, box2, etc...). Not sure if this will keep the animation, but you can take a look at an example here. Just watch the address bar.

Categories