Problem
I created a counter using HTML, CSS and JS (such as satisfied customer numbers, branch numbers, etc.)
The counter is also animated but since it's down the page, I'd like to animate it only when it gets to that point on the page. How do I do with the js?
const counters = document.querySelectorAll('.value');
const speed = 400;
counters.forEach( counter => {
const animate = () => {
const value = +counter.getAttribute('akhi');
const data = +counter.innerText;
const time = value / speed;
if(data < value) {
counter.innerText = Math.ceil(data + time);
setTimeout(animate, 1);
}else{
counter.innerText = value;
}
}
animate();
});
.counter-box {
display: block;
background: #f6f6f6;
padding: 40px 20px 37px;
text-align: center
}
.counter-box p {
margin: 5px 0 0;
padding: 0;
color: #909090;
font-size: 18px;
font-weight: 500
}
.counter {
display: block;
font-size: 32px;
font-weight: 700;
color: #666;
line-height: 28px
}
.counter-box.colored {
background: #eab736;
}
.counter-box.colored p,
.counter-box.colored .counter {
color: #fff;
}
<div class="container">
<div class="row contatore">
<div class="col-md-4">
<div class="counter-box colored">
<span class="counter value" akhi="560">0</span>
<p>Countries visited</p>
</div>
</div>
<div class="col-md-4">
<div class="counter-box">
<span class="counter value" akhi="3275">0</span>
<p>Registered travellers</p>
</div>
</div>
<div class="col-md-4">
<div class="counter-box">
<span class="counter value" id="conta" akhi="289">0</span>
<p>Partners</p>
</div>
</div>
</div>
</div>
What I have tried
i had tried with
const target = document.querySelector('.counter');
observer.observe(target);
but it doesn't seem to work. Many thanks to whoever can help me.
I would recommend, as others have suggested, to use the Intersection Observer API to animate your elements once they appear in the viewport.
The idea is simple, we'll create an observer that will observe the counters to animate and we're going to configure it so that it calls the animate function once a counter is fully visible in the viewport.
You may learn more about the options that an IntersectionObserver can accept in order to customize its behavior. Meanwhile, here's a live demo that illustrates how to make the counters animate once they appear in the screen (the code below has some helpful comments):
const counters = document.querySelectorAll('.value'),
speed = 400,
/**
* create an IntersectionObserver with the specified callback that will be executed for each intersection change for every counter we have.
* You may customize the options (2nd argument) per you requirement
*/
observer = new IntersectionObserver(
entries => entries.forEach(entry => entry.isIntersecting && animate(entry.target)),
{
threshold: 1 // tells the browser that we only need to execute the callback only when an element (counter) is fully visible in the viewport
}
),
// the animate function now accepts a counter (HTML element)
animate = counter => {
const value = +counter.dataset.akhi,
data = +counter.innerText,
time = value / speed;
if (data < value) {
counter.innerText = Math.ceil(data + time);
setTimeout(() => animate(counter), 1);
} else {
counter.innerText = value;
}
};
// attach the counters to the observer
counters.forEach(c => observer.observe(c));
.counter-box {
display: block;
background: #f6f6f6;
padding: 40px 20px 37px;
text-align: center
}
.counter-box p {
margin: 5px 0 0;
padding: 0;
color: #909090;
font-size: 18px;
font-weight: 500
}
.counter {
display: block;
font-size: 32px;
font-weight: 700;
color: #666;
line-height: 28px
}
.counter-box.colored {
background: #eab736;
}
.counter-box.colored p,
.counter-box.colored .counter {
color: #fff;
}
<div class="container">
<div class="row contatore">
<div class="col-md-4">
<div class="counter-box colored">
<!-- it is recommended to use "data-*" attributes to cache data that we might use later. The "data-akhi" contains the number to animate -->
<span class="counter value" data-akhi="560">0</span>
<p>Countries visited</p>
</div>
</div>
<div class="col-md-4">
<div class="counter-box">
<span class="counter value" data-akhi="3275">0</span>
<p>Registered travellers</p>
</div>
</div>
<div class="col-md-4">
<div class="counter-box">
<span class="counter value" id="conta" data-akhi="289">0</span>
<p>Partners</p>
</div>
</div>
</div>
</div>
As others suggested, you should use Intersection Observer.
This is how I'd do:
Scrolldown the snippet in order to see the counter animating up once is on the screen.
const counters = document.querySelectorAll('.value');
const speed = 400;
const observer = new IntersectionObserver( items => {
if(items[0].isIntersecting) {
const target = items[0].target;
const animate = () => {
const value = + target.getAttribute('akhi');
const data = + target.innerText;
const time = value / speed;
if(data < value) {
target.innerText = Math.ceil(data + time);
setTimeout(animate, 1);
}else{
target.innerText = value;
}
}
animate();
observer.unobserve(target);
}
})
counters.forEach( counter => observer.observe(counter));
.counter-box {
display: block;
background: #f6f6f6;
padding: 40px 20px 37px;
text-align: center
}
.counter-box p {
margin: 5px 0 0;
padding: 0;
color: #909090;
font-size: 18px;
font-weight: 500
}
.counter {
display: block;
font-size: 32px;
font-weight: 700;
color: #666;
line-height: 28px
}
.counter-box.colored {
background: #eab736;
}
.counter-box.colored p,
.counter-box.colored .counter {
color: #fff;
}
<div style="height: 600px;">
</div>
<div class="container">
<div class="row contatore">
<div class="col-md-4">
<div class="counter-box colored">
<span class="counter value" akhi="560">0</span>
<p>Countries visited</p>
</div>
</div>
<div class="col-md-4">
<div class="counter-box">
<span class="counter value" akhi="3275">0</span>
<p>Registered travellers</p>
</div>
</div>
<div class="col-md-4">
<div class="counter-box">
<span class="counter value" id="conta" akhi="289">0</span>
<p>Partners</p>
</div>
</div>
</div>
</div>
Related
I can't figure out how to close one submenu when another one is open. I'm not sure if html is needed here, so I'm just attaching JS code here:
const burgerBtn = document.querySelector(".header__burger"),
menu = document.querySelector(".menu"),
body = document.querySelector(".body"),
filter = document.querySelector(".filter"),
blockFilter = document.querySelectorAll(".block-filter"),
dropdown = document.querySelectorAll(".block-filter__dropdown");
if (filter) {
blockFilter.forEach(item => {
item.addEventListener("click", event => {
item.querySelector(".block-filter__dropdown").classList.toggle("block-filter__dropdown_state_active");
item.querySelector(".block-filter__icon").classList.toggle("block-filter__icon_state_active");
if (event.target.classList.contains("block-filter__item")) {
item.querySelector(".block-filter__value").textContent = event.target.textContent;
}
})
})
}
<div class="filter hero__filter">
<form class="filter__form">
<div class="filter__block block-filter">
<div class="block-filter__button">
<div class="block-filter__header">
<span class="block-filter__type">Purpose</span>
<div class="block-filter__icon"></div>
</div>
<span class="block-filter__value">Buy</span>
</div>
<div class="block-filter__dropdown">
<span class="block-filter__item">Buy</span>
<span class="block-filter__item">Sell</span>
</div>
</div>
Sure, just remove the class from the active one first:
item.addEventListener("click", (event) => {
// get active, and if it exists, remove active
document.querySelector(".block-filter__dropdown_state_active")?.classList.remove("block-filter__dropdown_state_active");
item.querySelector(".block-filter__dropdown").classList.toggle(
"block-filter__dropdown_state_active"
);
item.querySelector(".block-filter__icon").classList.toggle(
"block-filter__icon_state_active"
);
if (event.target.classList.contains("block-filter__item")) {
item.querySelector(".block-filter__value").textContent =
event.target.textContent;
}
});
We use ?. here to prevent us from going further (and causing an error) if there is no active dropdown already.
What you need to do is look for a currently active item first and "de-activate" them. You should also check that the currently active item is not the clicked item as you already have logic defined for that.
I've expanded on your snippet to create a solution.
NOTE: It might be useful creating a separate function/s for handling to "activate" and "de-activate" code where you pass in a .block-filter element.
const burgerBtn = document.querySelector(".header__burger"),
menu = document.querySelector(".menu"),
body = document.querySelector(".body"),
filter = document.querySelector(".filter"),
blockFilter = document.querySelectorAll(".block-filter"),
dropdown = document.querySelectorAll(".block-filter__dropdown");
if (filter) {
blockFilter.forEach(item => {
item.addEventListener("click", event => {
const active_dropdown = document.querySelector(".block-filter__dropdown_state_active");
if(active_dropdown !== null){
// get parent until we find ".block-filter"
const active_item = active_dropdown.closest(".block-filter");
// check it's not the current item
if(active_item !== null && active_item !== item){
// apply same logic as below to remove active state
active_item.querySelector(".block-filter__dropdown").classList.remove("block-filter__dropdown_state_active");
active_item.querySelector(".block-filter__icon").classList.remove("block-filter__icon_state_active");
}
}
// your original logic
item.querySelector(".block-filter__dropdown").classList.toggle("block-filter__dropdown_state_active");
item.querySelector(".block-filter__icon").classList.toggle("block-filter__icon_state_active");
if (event.target.classList.contains("block-filter__item")) {
item.querySelector(".block-filter__value").textContent = event.target.textContent;
}
})
})
}
/* base styles */
* {
box-sizing: border-box;
}
html {
font-family: sans-serif;
background-color: #f3f3f3;
}
.filter.hero__filter {
width:600px;
margin:auto;
border: 2px solid #eee;
background-color: #fff;
}
.filter__form {
display:flex;
}
.filter__block {
flex: 1;
padding: 5px;
position: relative;
}
.block-filter__header {
font-weight:600;
font-size:12px;
color: #555;
}
.block-filter__dropdown {
display:none;
position:absolute;
top:100%;
left:0;
right:0;
background-color:#fff;
border: 1px solid #ccc;
box-shadow: 0 2px 4px rgb(0 0 0 / 10%);
border-radius:4px;
}
.block-filter__dropdown_state_active {
display: block;
}
.block-filter__item {
padding: 5px 10px;
display:block;
border-bottom: 1px solid #eee;
}
.block-filter__item:last-child {
border-bottom: none;
}
<div class="filter hero__filter">
<form class="filter__form">
<div class="filter__block block-filter">
<div class="block-filter__button">
<div class="block-filter__header">
<span class="block-filter__type">Purpose</span>
<div class="block-filter__icon"></div>
</div>
<span class="block-filter__value">Buy</span>
</div>
<div class="block-filter__dropdown">
<span class="block-filter__item">Buy</span>
<span class="block-filter__item">Sell</span>
</div>
</div>
<div class="filter__block block-filter">
<div class="block-filter__button">
<div class="block-filter__header">
<span class="block-filter__type">Second</span>
<div class="block-filter__icon"></div>
</div>
<span class="block-filter__value">Alpha</span>
</div>
<div class="block-filter__dropdown">
<span class="block-filter__item">Bravo</span>
<span class="block-filter__item">Charlie</span>
<span class="block-filter__item">Delta</span>
</div>
</div>
</form>
</div>
I want to stop incrementing the number of individual likes if the photo was liked(clicked) once, and increment the total number of likes for each individual photo liked(clicked)
individual photo likes likesAfterAddition
global photo likes globalNumberOfLikes
For the moment it is increasing every time I click in both individual and global likes, I know it is not the right logic!
What logic can I use please?
//increment likes on click
function incrementLikesOnClick() {
const heartIcons = Array.from(document.getElementsByClassName('heartIcon')); // multiple heart icons
heartIcons.forEach((likeIcon, index) => likeIcon.addEventListener('click', () => {
const individualLikeBox = document.getElementsByClassName('under-photo-info');
const totalLikesDivBox = document.getElementById("likesBox");
likeIcon.classList.add('activeRed');
let likesAfterAddition = likesTable[index] + 1; // add 1 like to the individual current photo
likesTable.splice(index, 1, likesAfterAddition); // replace the old value from the Array with the new value
let sum = likesTable.reduce(function(a, b){return a + b;}); // return the sum of the array
let globalNumberOfLikes = sum; // the sum of the array
individualLikeBox[index].innerHTML = `<span'>${likesAfterAddition}</span>`
totalLikesDivBox.innerHTML = `<div class="Likes">${globalNumberOfLikes}<i class="fas fa-heart"></i></div>`
console.log(likesTable)
}))
}
instead of using for loop to set event listeners which is not efficient
you can use the feature of bubbling, so when any of dom element is clicked, the event will bubble up of its parent elements sequentially till it reaches the parent dom
//increment likes on click
function incrementLikesOnClick() {
document.addEventListener("DOMContentLoaded", function(event) {
// Your code to run since DOM is loaded and ready
document.addEventListener('click', () => {
let clicked = event.target;
//element with class heartIcon is clicked and it doesnt have activeRed class
if(clicked.classList.contains('heartIcon') && !clicked.classList.contains('activeRed')){
let productContainer = clicked.parentElement.parentElement; // till you reach the product container
const individualLikeBox = productContainer.getElementsByClassName('under-photo-info');
const totalLikesDivBox = productContainer.getElementById("likesBox");
clicked.classList.add('activeRed');
// ..whatever extra logic you want to add
}
});
});
}
If the like icon is a button (which I assume it is). U can just add a 'disabled' attribute to it as part of the event handler (for the 'click' eventListener).
'When present, it specifies that the button should be disabled.
A disabled button is unusable and un-clickable.' (source)
I would calculate the total likes based on the presence of an "active" class on each button.
const totalLikesEl = document.querySelector('#total-likes');
const updateTotalLikes = () => {
totalLikesEl.textContent = document.querySelectorAll('.like.active').length;
};
const toggleLike = (e) => {
const button = e.currentTarget.classList.toggle('active');
updateTotalLikes();
};
document.querySelectorAll('.like').forEach(likeBtn => {
likeBtn.addEventListener('click', toggleLike);
});
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
body {
display: flex;
flex-direction: column;
}
.cards {
display: flex;
flex-direction: row-wrap;
justify-content: center;
}
.card {
display: flex;
flex-direction: column;
padding: 0.25em;
margin: 0.5em;
border: thin solid grey;
}
.card-content {
background: grey;
width: 6em;
height: 6em;
}
.card-actions {
display: flex;
flex-direction: row;
justify-content: flex-end;
margin-top: 0.5em;
}
.like > .fa-heart {
color: grey;
}
.like.active > .fa-heart {
color: red;
}
.example-1 .card-content {
background: rgb(63,94,251);
background: radial-gradient(circle, rgba(63,94,251,1) 0%, rgba(252,70,168,1) 100%);
}
.example-2 .card-content {
background: rgb(251,63,94);
background: radial-gradient(circle, rgba(251, 63,94,1) 0%, rgba(168,252,70,1) 100%);
}
.example-3 .card-content {
background: rgb(94,63,251);
background: radial-gradient(circle, rgba(94,63,251,1) 0%, rgba(70,252,168,1) 100%);
}
.status {
text-align: center;
margin-top: 0.5em;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.2/css/all.min.css" rel="stylesheet"/>
<div class="cards">
<div class="card example-1">
<div class="card-content"></div>
<div class="card-actions">
<button class="like">
Like <i class="fas fa-heart"></i>
</button>
</div>
</div>
<div class="card example-2">
<div class="card-content"></div>
<div class="card-actions">
<button class="like">
Like <i class="fas fa-heart"></i>
</button>
</div>
</div>
<div class="card example-3">
<div class="card-content"></div>
<div class="card-actions">
<button class="like">
Like <i class="fas fa-heart"></i>
</button>
</div>
</div>
</div>
<div class="status">
<strong>Total Likes:</strong>
<span id="total-likes">0</span>
</div>
How to use scrollIntoView on container which has overflow:hidden and it shouldn't scroll the page?
Here is an example: at the bottom of the page text in container <div class="cnt'> which has fixed width and overflow hidden. I want to scroll items in this container without scrolling the page.
At the top of the page two buttons to scroll to first and last element. If i click on button it will scroll text in the container and scroll to that container at the bottom of the page.
I can't use scrollLeft because overflow is hidden. :(
Does anybody know how to solve it?
const cnt = document.querySelector('.cnt')
const spanElements = cnt.querySelectorAll('span');
const lastSpan = spanElements[spanElements.length - 1]
const firstSpan = spanElements[0]
lastSpan.scrollIntoView()
const buttons = Array.from(document.querySelectorAll('button'))
const [buttonToFirstEl, buttonToLastEl] = buttons;
buttonToFirstEl.onclick = function() {
firstSpan.scrollIntoView()
}
buttonToLastEl.onclick = function() {
lastSpan.scrollIntoView()
}
.cnt {
width: 90px;
overflow: hidden;
white-space: nowrap;
padding: 8px;
border: solid #ccc 1px;
}
.filler {
width: 100%;
height: 200px;
border: dashed 2px #ccc;
margin: 20px;
}
.root {
border: solid 1px;
}
<div class="root">
<button id="button">scroll to first element</button>
<button id="button">scroll to last element</button>
<div class="filler">
</div>
<div class="filler">
</div>
<div class="filler">
</div>
<div class="filler">
</div>
<div class="cnt">
<span>
first:tessst
</span>
<span>
2:dddd
</span>
<span>
3:cccddd
</span>
<span>
4:rreeee
</span>
<span>
last:dddrreddd
</span>
</div>
</div>
https://codepen.io/geeny273/pen/bGpxYqG
if you want scrolled element in .cnt class
you need to style every span in .cnt
.cnt span{
/* relative + block for make elements solid & listed*/
position : relative;
display : block;
color: red;
font-size : 16px;
}
and in .cnt parent you need to defined height for making scroll working
.cnt {
width: 90px;
/* for example 28px */
height : 28px;
overflow: hidden;
white-space: nowrap;
padding: 4px;
border: solid black 1px;
/* overflow y or x for making scroll*/
overflow-y : scroll;
}
i hope this steps help you
To get this correctly, (i.e with support for all writing-mode, direction and block-inline options) you'd basically have to rewrite scrollIntoView from scratch, while omitting the loop that goes up the tree of scrolling-boxes. This is not a trivial task.
If you don't need a bullet proof solution though, you can simply get the nearest scrolling-box and check by how much it should be scrolled to show the start of your element:
const cnt = document.querySelector('.cnt')
const spanElements = cnt.querySelectorAll('span');
const lastSpan = spanElements[spanElements.length - 1]
const firstSpan = spanElements[0]
const buttons = Array.from(document.querySelectorAll('button'))
const [buttonToFirstEl, buttonToLastEl] = buttons;
buttonToFirstEl.onclick = function() {
scrollIntoParentView( firstSpan );
}
buttonToLastEl.onclick = function() {
scrollIntoParentView( lastSpan );
}
function scrollIntoParentView( elem, options ) {
const directions = getSimpleScrollIntoViewDirections( elem, options );
const left = directions.inline_direction || 0;
const top = directions.block_direction || 0;
const new_options = {
left,
top,
behavior: (options && options.behavior) || "auto"
};
directions.scrolling_box.scrollBy( new_options );
}
function getSimpleScrollIntoViewDirections( elem, { block = "start", inline = "start" } = {} ) {
const element_bbox = elem.getBoundingClientRect();
const scrolling_box = getNearestScrollingBox( elem );
const scrolling_box_bbox = scrolling_box.getBoundingClientRect();
const block_direction = Math.round( element_bbox.top - scrolling_box_bbox.top);
const inline_direction = Math.round( element_bbox.left - scrolling_box_bbox.left);
return {
scrolling_box,
block_direction,
inline_direction
};
}
function getNearestScrollingBox( elem ) {
if( !elem.isConnected ) { return null; }
const elem_computed_styles = getComputedStyle( elem );
// not in specs, but that seems to be what browser implementations do
if( elem_computed_styles.getPropertyValue( 'position' ) === "fixed" ) {
return null;
}
const scrolling_box = elem.parentElement;
if( !scrolling_box ) {
return elem === document.scrollingElement ? null : document.scrollingElement;
}
const computed_styles = getComputedStyle( scrolling_box );
const scroll_x = computed_styles.getPropertyValue( "overflow-x");
const scroll_y = computed_styles.getPropertyValue( "overflow-y");
if(
(scroll_x === 'clip' && scroll_y === 'clip') ||
(scrolling_box.offsetHeight <= scrolling_box.scrollingHeight) ||
(scrolling_box.offsetWidth <= scrolling_box.scrollingWidth)
) {
return getNearestScrollingBox( scrolling_box );
}
return scrolling_box;
}
.cnt {
width: 90px;
overflow: hidden;
white-space: nowrap;
padding: 8px;
border: solid #ccc 1px;
}
.filler {
width: 100%;
height: 200px;
border: dashed 2px #ccc;
margin: 20px;
}
.root {
border: solid 1px;
}
<div class="root">
<button id="button">scroll to first element</button>
<button id="button">scroll to last element</button>
<div class="filler">
</div>
<div class="filler">
</div>
<div class="filler">
</div>
<div class="filler">
</div>
<div class="cnt">
<span>
first:tessst
</span>
<span>
2:dddd
</span>
<span>
3:cccddd
</span>
<span>
4:rreeee
</span>
<span>
last:dddrreddd
</span>
</div>
</div>
scrollLeft and scrollTop work well with overflow:hidden. I check scrollLeft property on first span element instead of its container
changing scrollLeft on parent element of span, solve this issue
firstSpan.parentElement.scrollLeft = 0;
const cnt = document.querySelector('.cnt')
const spanElements = cnt.querySelectorAll('span');
const lastSpan = spanElements[spanElements.length - 1]
const firstSpan = spanElements[0]
lastSpan.scrollIntoView()
const buttons = Array.from(document.querySelectorAll('button'))
const [buttonToFirstEl, buttonToLastEl] = buttons;
buttonToFirstEl.onclick = function() {
firstSpan.parentElement.scrollLeft = 0;
}
buttonToLastEl.onclick = function() {
const cnt = lastSpan.parentElement;
cnt.scrollLeft = cnt.scrollWidth;
}
.cnt {
width: 90px;
overflow: hidden;
white-space: nowrap;
padding: 8px;
border: solid #ccc 1px;
}
.filler {
width: 100%;
height: 200px;
border: dashed 2px #ccc;
margin: 20px;
}
.root {
border: solid 1px;
}
<div class="root">
<button id="button">scroll to first element</button>
<button id="button">scroll to last element</button>
<div class="filler">
</div>
<div class="filler">
</div>
<div class="filler">
</div>
<div class="filler">
</div>
<div class="cnt">
<span>
first:tessst
</span>
<span>
2:dddd
</span>
<span>
3:cccddd
</span>
<span>
4:rreeee
</span>
<span>
last:dddrreddd
</span>
</div>
</div>
i working on mixitup plugin with jRange slider jquery plugin and is working good without jRange slider, but when i include jrange slider for mobiles price its not filter and not show product within the range. how to filter multiple selector. for example when click on iphone products , its show all iphone product but i want to also show iphone products with in price range. or any other products.
how to fix the problem.
jQuery(document).ready(function($) {
var price_mega = {};
main_filter();
$('.slider-input').jRange({
from: 0,
to: 400,
format: '$%s',
showLabels: true,
isRange : true,
onstatechange: function(value){
result = value.split(',');
price_mega = result;
main_filter();
}
});
function main_filter(){
$('.container').each(function(index, el) {
var min_price = Number(price_mega[0]);
var max_price = Number(price_mega[1]);
// console.log(min_price);
var active = $(this).data('active');
var wrap = $(this).closest('.main-wrapper');
var target = wrap.find('.target_filter');
var filter = wrap.find('.controls .filter');
// var filter = wrap.find('.container').find('.target_filter').filter(function(){
// var price = Number($(this).attr('data-price'));
// return price >= min_price && price <= max_price
// });
wrap.find('.container').mixItUp({
selectors: {
target: target,
filter: filter
},
load: {
filter: active,
}
});
});
}
});
.controls {
padding: 1rem;
background: #333;
font-size: 0.1px;
}
.controls button{
font-size: 27px;
color: gray;
margin-left: 20px;
}
.mixitup-control-active {
background: #393939;
}
.mixitup-control-active[data-filter]:after {
background: transparent;
}
.mix,
.gap {
display: inline-block;
vertical-align: top;
}
.mix {
background: #fff;
border-top: .5rem solid currentColor;
border-radius: 2px;
margin-bottom: 1rem;
position: relative;
display: none;
}
.mix.green {
color: #91e6c7;
}
.mix.pink {
color: #d595aa;
}
.mix.blue {
color: #5ecdde;
}
.mix,
.gap {
width: calc(100%/2 - (((2 - 1) * 1rem) / 2));
}
#media screen and (min-width: 541px) {
.mix,
.gap {
width: calc(100%/3 - (((3 - 1) * 1rem) / 3));
}
}
#media screen and (min-width: 961px) {
.mix,
.gap {
width: calc(100%/4 - (((4 - 1) * 1rem) / 4));
}
}
#media screen and (min-width: 1281px) {
.mix,
.gap {
width: calc(100%/5 - (((5 - 1) * 1rem) / 5));
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mixitup/2.1.11/jquery.mixitup.js"></script>
<script src="http://nitinhayaran.github.io/jRange/jquery.range.js"></script>
<link href="http://nitinhayaran.github.io/jRange/jquery.range.css" rel="stylesheet"/>
<div class="main-wrapper">
<div class="controls">
<button type="button" data-filter="all" class="filter">All</button>
<button type="button" data-filter=".samsung" class="filter">Samsung</button>
<button type="button" data-filter=".iphone" class="filter">Iphone</button>
<button type="button" data-filter=".blackberry" class="filter">Blackberry</button>
<div style="margin-top: 5%;height: 22px;">
<input type="hidden" class="filter slider-input" value="0,400" />
</div>
</div>
<div class="container" data-active=".samsung">
<div class="target_filter mix samsung" data-price="200">samsung 1(price $200)</div>
<div class="target_filter mix blackberry" data-price="111">blackberry 1(price $111)</div>
<div class="target_filter mix samsung" data-price="165">samsung 2(price $165)</div>
<div class="target_filter mix iphone" data-price="300">iphone 1(price $300)</div>
<div class="target_filter mix iphone" data-price="340">iphone 2 (price $340)</div>
<div class="target_filter mix samsung" data-price="100">samsung 3 (price $100)</div>
<div class="target_filter mix blackberry" data-price="89">blackberry 2(price $89)</div>
<div class="target_filter mix iphone" data-price="232">iphone 3(price $232)</div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
</div>
</div>
I am trying to learn about Javascript's IntersectionObserver.
After reading several articles and the documentation I have decided to make a CodePen to try it myself: IntersectionObserver CodePen
I would like to display the "block that is visible" on the top message. The CodePen "almost" works, but not completely. Sometimes it shows the correct block, sometimes it doesn't.
Here is my JS:
let message = document.querySelector('#block-number');
// INTERSECTION OBSERVER STUFF
const io = new IntersectionObserver(entries => {
if(entries[0].isIntersecting) {
message.innerHTML = entries[0].target.textContent;
}
}, {
threshold: [.25]
});
// ELEMENTS TO OBSERVE
const blk1 = document.querySelector('#block1');
const blk2 = document.querySelector('#block2');
const blk3 = document.querySelector('#block3');
const blk4 = document.querySelector('#block4');
const blk5 = document.querySelector('#block5');
const blk6 = document.querySelector('#block6');
// START OBSERVING ELEMENTS
io.observe(blk1);
io.observe(blk2);
io.observe(blk3);
io.observe(blk4);
io.observe(blk5);
io.observe(blk6);
Any ideas on what i am doing wrong?
i have also tried (without luck) something like:
if(entries[0].intersectionRatio !== 0)
Thank you!
The function passed to the IntersectionObserved is executed when the intersection state changes. So what happens when you are at block 3 and scroll a bit so block 4 is shown? The intersection changes for block 4 and so the message is changed. WHen you scroll back up the intersection is changed again for block 4, but it does not enter the if condition. The intersection for block 3 on the other hand is not changed - it was visible before, even though not fully, it's visible still.
There are few ways you can fix this.
One is to define intersection ratio, and going above and below that ratio will be considered change in the state (pass options hash as second argument, containing threshold key with value 0 - 1, e.g. 0.5 for 50% visibility)
You can also add the same observer for all of the blocks and iterate trough entries in the function, checking which block has the best intersection ratio.
You have set the threshold to 25%.
The problem with this, is that the previous block leaves its last 25% of the viewport after the next block enters the viewport by 25%.
This was easy to see with the following console.log:
console.log(entries[0].target.textContent, ": ", entries[0].intersectionRatio)
let message = document.querySelector('#block-number');
// INTERSECTION OBSERVER STUFF
const io = new IntersectionObserver(entries => {
if(entries[0].isIntersecting ) {
console.log(entries[0].target.textContent, ": ", entries[0].intersectionRatio)
message.innerHTML = entries[0].target.textContent;
}
}, {
threshold: [.25]
});
// ELEMENTS TO OBSERVE
const blk1 = document.querySelector('#block1');
const blk2 = document.querySelector('#block2');
const blk3 = document.querySelector('#block3');
const blk4 = document.querySelector('#block4');
const blk5 = document.querySelector('#block5');
const blk6 = document.querySelector('#block6');
// START OBSERVING ELEMENTS
io.observe(blk1);
io.observe(blk2);
io.observe(blk3);
io.observe(blk4);
io.observe(blk5);
io.observe(blk6);
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: roboto;
}
.center {
display: flex;
align-items: center;
justify-content: center;
}
.container {
background-color: #eee;
width: 100%;
height: 100%;
min-height: 100vh;
}
.message {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 80px;
background-color: #ef9b8d;
color: white;
}
.blocks {
padding-top: 100px;
}
.block {
height: 85vh;
width: 90vw;
margin: 0 auto 15vh;
background-color: #999;
color: white;
}
<div class="message center">Displaying <span id="block-number">Block 1</span></div>
<div class="blocks">
<div id="block1" class="block center">Block 1</div>
<div id="block2" class="block center">Block 2</div>
<div id="block3" class="block center">Block 3</div>
<div id="block4" class="block center">Block 4</div>
<div id="block5" class="block center">Block 5</div>
<div id="block6" class="block center">Block 6</div>
</div>
To fix this, simply raise the threshold. (Depending on how much of the block needs to enter the viewport for you to consider it the current block)
Demo:
let message = document.querySelector('#block-number');
// INTERSECTION OBSERVER STUFF
const io = new IntersectionObserver(entries => {
if(entries[0].isIntersecting ) {
console.log(entries[0].target.textContent, ": ", entries[0].intersectionRatio)
message.innerHTML = entries[0].target.textContent;
}
}, {
threshold: [.8] // raised the threshold
});
// ELEMENTS TO OBSERVE
const blk1 = document.querySelector('#block1');
const blk2 = document.querySelector('#block2');
const blk3 = document.querySelector('#block3');
const blk4 = document.querySelector('#block4');
const blk5 = document.querySelector('#block5');
const blk6 = document.querySelector('#block6');
// START OBSERVING ELEMENTS
io.observe(blk1);
io.observe(blk2);
io.observe(blk3);
io.observe(blk4);
io.observe(blk5);
io.observe(blk6);
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: roboto;
}
.center {
display: flex;
align-items: center;
justify-content: center;
}
.container {
background-color: #eee;
width: 100%;
height: 100%;
min-height: 100vh;
}
.message {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 80px;
background-color: #ef9b8d;
color: white;
}
.blocks {
padding-top: 100px;
}
.block {
height: 85vh;
width: 90vw;
margin: 0 auto 15vh;
background-color: #999;
color: white;
}
<div class="message center">Displaying <span id="block-number">Block 1</span></div>
<div class="blocks">
<div id="block1" class="block center">Block 1</div>
<div id="block2" class="block center">Block 2</div>
<div id="block3" class="block center">Block 3</div>
<div id="block4" class="block center">Block 4</div>
<div id="block5" class="block center">Block 5</div>
<div id="block6" class="block center">Block 6</div>
</div>