Refactor jQuery inview event listener - javascript

I want to convert the following code to pure JavaScript.
$(function () {
$('h2,.single').on('inview', function () {
$(this).addClass('is-show');
});
});
I tried much time but still can not figure out how. Any helps?

Inview was an old plugin to solve a problem that now has a web API, as long as you don't need to support IE you can use intersection observer to do this.
Without any example or further explanation of how you need things to function it's hard to guess what you want to achieve. But here's a basic implementation that would mimic the tiny bit of JQuery you provided.
const sections = [].slice.call(document.querySelectorAll(".single"));
function createObserver(el) {
let observer;
const options = {
root: null,
rootMargin: "0px",
threshold: 0.5
};
observer = new IntersectionObserver(handleIntersect, options);
observer.observe(el);
}
function handleIntersect(entries, observer) {
entries.forEach((entry) => {
let box = entry.target;
let visible = entry.intersectionRatio;
if(visible > 0.5) {
box.classList.add('is-show');
} else {
box.classList.remove('is-show');
}
});
}
const setup = (sections) => {
for (let i in sections) {
const el = sections[i];
createObserver(el);
}
}
setup(sections);
.single {
padding: 2rem;
background: tomato;
color: #fff;
margin: 600px 0;
transition: all .5s ease-out;
opacity: 0;
}
.is-show {
opacity: 1
}
<h2 class="single">I'm an H2 Element in frame</h2>
<h2 class="single">I'm an H2 Element in frame</h2>
<h2 class="single">I'm an H2 Element in frame</h2>
<h2 class="single">I'm an H2 Element in frame</h2>

Except the event you can use this:
(function() {
document.querySelector("h2, .single").addEventListener("click", function(){
this.classList.add("is-show");
});
})();
I didn't find an equivalent for inview. You can refer to this.

Related

How to highlight the current section the user is viewing in javascript?

I am coding a simple navigation bar for a project that has four sections, and I made it interactive enough to have a specific color when hovering/clicking on a section and then it returns back to its original color after clicking.
But what if I want the selected section to still be colored/highlighted when a user is viewing it?
So if the hovering color is coded blue, i want the section in the Navbar to still be blue when a user has selected it, and then changes when a user selects another section. Here's my code so far.
// The mouse hover functiona and commands. Here we specificy the color of the buttons/mouse
// when the user clicks on them, there's a color for hovering/clicking
// and a color for leaving the button
function mouseOver () {
let anchor = document.getElementsByTagName('a');
for (i = 0; i < anchor.length; i++) {
anchor[i].addEventListener('mouseover', function handleMouseOver() {
event.target.style.backgroundColor = "#72a6ca";
event.target.style.color = "#fff";
})
//the color returns to its normal state after clicking away
anchor[i].addEventListener('mouseout', function handleMouseOut() {
event.target.style.backgroundColor = "rgb(220, 220, 220)";
event.target.style.color = "black";
})
}
}
and here is my navbar display code
function navBarStyle () {
let anchor = document.getElementsByTagName('a');
let styles = `
display: flex;
flex-direction: row;
align-items: stretch;
color: #000;
text-decoration: none;
margin: 0 0.5em 0 0.5em;
padding: 0.5em;
background-color: rgb(220, 220, 220);
font-size: large;
transform:translateX(-0.5em);
`;
for (i = 0; i < anchor.length; i++) {
anchor[i].setAttribute('style', styles);
} }
if i was vague enough i am sorry, but any help would be appreciated to put me on the right track
Firstly, a note for your current implementation. It works and it is pretty well coded. But for this thing browsers offer native functionality using the :hover selector and it would be better to use than to reinvent it.
I don't have your HTMl but you would most likely need to add a class to each 'a' tag in the nav, something like this:
<nav>
Link 1
Link 2
</nav>
and then you would need a style tag in the head (or better, external css)
<head>
...
<style>
.nav-link {
background-color: 72a6ca;
color: #fff;
}
.nav-link:hover {
background-color: rgb(220, 220, 220);
color: black;
}
</style>
</head>
As for the current section, your best bet would be to use https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
See here for an example: Intersection observer API scroll aware navigation
or this codepen: https://codepen.io/mishunov/pen/opeRdL
Using IntersectionObserver you can detect when the user scrolls in/out of the section. You can toggle another class on and off of the related nav-link then. For example - say you toggle the .current class, your style could look like this to style both cases (hovering and currently scrolled) in 1 place:
.nav-link:hover,
.nav-link.current {
background-color: rgb(220, 220, 220);
color: black;
}
You can make a class named active like this
.active {
backgroundColor: #72a6ca;
color: #fff;
}
and assign it to each anchor that's clicked(or hovered), simultaneously remove .active from the other anchors
let anchors = document.getElementsByTagName('a');
for (let anchor of anchors) {
anchor.addEventListener('mouseover', function handleMouseOver() {
const target = event.currentTarget;
if (target.classList.contains('active')) {
target.classList.remove('active')
} else {
[...anchors].forEach((anchor) => anchor.classList.remove('active'))
target.classList.add('active')
}
})
}
If you want to give the class active to the anchors in viewPort use this code:
const anchors = document.getElementsByTagName('a');
const isInViewport = el => {
const rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <=
(window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
};
const run = () =>
anchors.forEach(item => {
if (isInViewport(item)) {
item.classList.add('active');
}
});
// Events
window.addEventListener('load', run);
window.addEventListener('resize', run);
window.addEventListener('scroll', run);

Make animation happen when a section is in view using javascript

I am still learning web designing and I wanted to start an animation on a div which is in middle of the page. I searched for it but everywhere I found it using j-query. Is there any way it can be done using pure CSS and JavaScript.
<div>A lot of contents which take whole screen</div>
<div>Section where animation has to happen when come into view</div>
Please help if it can be done using javascript only and if not then what is the easiest way of doing it.
I just searched like you did and found a pure js answer
REFERENCE
var elements;
var windowHeight;
document.getElementById('content').innerText = "A lot of content to fill up the page. ".repeat(500)
function init() {
elements = document.querySelectorAll('.noanimfornow');
windowHeight = window.innerHeight;
}
function checkPosition() {
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
var positionFromTop = elements[i].getBoundingClientRect().top;
//console.log(positionFromTop,windowHeight);
if (positionFromTop - windowHeight <= 0) {
element.classList.add('animateme');
element.classList.remove('noanimfornow');
}
if (positionFromTop - windowHeight > 0) {/*newly added:Edit2*/
element.classList.add('noanimfornow');
element.classList.remove('animateme');
}
}
}
window.addEventListener('scroll', checkPosition);
window.addEventListener('resize', init);
init();
checkPosition();
#keyframes myanim {
from {
opacity: 0;
transform: scale(.7, .7)
}
to {
opacity: 1;
}
}
.animateme {
animation: myanim 5s;
}
.noanimfornow {
opacity: 0;
}
<div id="content"></div>
<div class="noanimfornow">Section where animation has to happen when come into view</div>
First for the div you need animation only when you scroll, set class to noanimfornow ,
Next in js we check the position of scroll, and set the class to animateme when it reaches into view,
We also check any resizing event and start init function if needed in js
finally we put some animation for those in css
This answer does exactly the same as the other answer, but it uses IntersectionObserver
Thus "No need to enter JS on every scroll." - A Haworth's comment
This Code is referred from here
also i have used Tschallacka's edit to remove copy paste (using .repeat(500) in js)
var elements;
var windowHeight;
document.getElementById('content').innerText = "A lot of content to fill up the page. ".repeat(500)
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
const square = entry.target.querySelector('.noanimfornow');
if (entry.isIntersecting) {
square.classList.add('animateme');
return; // if we added the class, exit the function
}
// We're not intersecting, so remove the class!
square.classList.remove('animateme');
});
});
observer.observe(document.querySelector('.animwrapper'));
#keyframes myanim {
from {
opacity: 0;
transform: scale(.7, .7)
}
to {
opacity: 1;
}
}
.animateme {
animation: myanim 5s;
}
.noanimfornow {
opacity: 0;
}
<div id="content"></div>
<div class="animwrapper">
<div class="noanimfornow">Section where animation has to happen when come into view</div>
</div>

single intersection observer for multiple entries

cannot fully understand the IntersectionObserver
in the example below, everything works fine, but I'm trying to write only one single observer for multiple entries
and I'm getting various error messages.
Pls, help
let io = new IntersectionObserver((entries)=>{
entries.forEach(entry=>{
if(entry.isIntersecting){navt.classList.remove('navt1');}
else{navt.classList.add('navt1');}
})
})
let io2 = new IntersectionObserver((entries)=>{
entries.forEach(entry=>{
if(entry.isIntersecting){gotopw.style.display = 'block';}
else{gotopw.style.display = 'none';}
})
})
$(document).ready(function(){
io.observe(document.querySelector('#wrapt'));
io2.observe(document.querySelector('#apanel'));
});
Every intersecting entity refers to the element that is intersecting. So to create a single IntersectionObserver you simply have to take advantage of that.
This is a simplified example to show the concept. Note there are two "boxes" that can scroll into view. As they scroll into view the background color changes individually. I used an intersection ratio so you can see the change happen.
The modify() and revert() functions represent operations you would perform in one of the two intersection thresholds.
The test for the element id is the trick that allows the use of one IntersectionObserver for multiple elements.
Scroll slowly to see both boxes.
let io = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting && entry.intersectionRatio > 0.5) {
modify(entry.target);
} else {
revert(entry.target);
}
})
}, {
threshold: 0.5
})
function modify(el) {
if (el.id === "wrapt") {
el.style.backgroundColor = 'red';
}
if (el.id === "apanel") {
el.style.backgroundColor = 'green';
}
}
function revert(el) {
if (el.id === "wrapt") {
el.style.backgroundColor = 'initial';
}
if (el.id === "apanel") {
el.style.backgroundColor = 'initial';
}
}
io.observe(document.querySelector('#wrapt'));
io.observe(document.querySelector('#apanel'));
#wrapt {
border: 2px solid black;
height: 100px;
width: 100px;
}
#apanel {
border: 2px solid blue;
height: 100px;
width: 100px;
}
.empty {
height: 400px;
width: 100px;
}
<div class="empty"> </div>
<div id="wrapt">Wrapt</div>
<div class="empty"></div>
<div id="apanel">aPanel</div>

Change button color based on screen size

What I am trying to achieve is when my device size is less than 736 px, the button should animate. I got the button working correctly, however, I’m struggling to work with the specific screen size.
$(window).resize(function() {
if ($(window).width() <= 736) {
// do something
let myBtn = document.querySelector(".btn");
let btnStatus = false;
myBtn.style.background = "#FF7F00";
function bgChange() {
if (btnStatus == false) {
myBtn.style.background = "#FF0000";
btnStatus = true;
}
else if (btnStatus == true) {
myBtn.style.background = "#FF7F00";
btnStatus = false;
}
}
myBtn.onclick = bgChange;
}
});
.btn {
width: 200px;
height: 100px;
text-align: center;
padding: 40px;
text-decoration: none;
font-size: 20px;
letter-spacing: .6px;
border-radius: 5px;
border: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button class="btn">CLICK ME</button>
Here's an implementation of what you're trying to do that uses:
class to alter button styling instead of style,
vanilla JavaScript instead of jQuery.
Using class is a good idea, as it keeps the styling in the CSS and out of the JavaScript code.
Using vanilla JavaScript whenever you can is preferable.
Here are the two new classes:
.btn-small-screen {
background: #FF7F00;
}
.btn-clicked {
background: #FF0000;
}
.btn-small-screen class is applied when the window is small, .btn-clicked is toggled whenever the button is clicked.
Here's the JavaScript code:
let myBtn = document.querySelector('.btn');
let isSmallWindow = () => window.innerWidth <= 736;
function toggleButtonOnClick () {
myBtn.classList.toggle('btn-clicked');
}
function setButtonMode () {
if (isSmallWindow()) {
myBtn.classList.add('btn-small-screen');
myBtn.addEventListener('click', toggleButtonOnClick);
} else {
myBtn.classList.remove('btn-small-screen');
myBtn.classList.remove('btn-clicked');
myBtn.removeEventListener('click', toggleButtonOnClick);
}
}
// setup mode on resize
window.addEventListener('resize', setButtonMode);
// setup mode at load
window.addEventListener('load', setButtonMode);
References:
Document.querySelector()
Window.innerWidth
Element.classList
DOMTokenList.toggle()
DOMTokenList.add()
DOMTokenList.remove()
EventTarget.addEventListener()
A working example:
let myBtn = document.querySelector('.btn');
let isSmallWindow = () => window.innerWidth <= 736;
function toggleButtonOnClick () {
myBtn.classList.toggle('btn-clicked');
}
function setButtonMode () {
if (isSmallWindow()) {
myBtn.classList.add('btn-small-screen');
myBtn.addEventListener('click', toggleButtonOnClick);
} else {
myBtn.classList.remove('btn-small-screen');
myBtn.classList.remove('btn-clicked');
myBtn.removeEventListener('click', toggleButtonOnClick);
}
}
// setup small mode on resize
window.addEventListener('resize', setButtonMode);
// setup small mode at load
window.addEventListener('load', setButtonMode);
.btn {
width: 200px;
height: 100px;
text-align: center;
padding: 40px;
text-decoration: none;
font-size: 20px;
letter-spacing: .6px;
border-radius: 5px;
border: none;
}
.btn-small-screen {
background: #FF7F00;
}
.btn-clicked {
background: #FF0000;
}
<button class="btn">CLICK ME</button>
Note: There is one optimization that I left out, so the code would be easier to follow.
Notice that setButtonMode() changes the DOM every time, even though it might already be set to the desired mode. This is inefficient.
To improve efficiency and only change the DOM when necessary, you could introduce a state variable (call it smallMode), and set it true whenever appropriate. Like so:
let smallMode = false;
function setButtonMode () {
if (isSmallWindow()) {
if (!smallMode) {
myBtn.classList.add('btn-small-screen');
myBtn.addEventListener('click', toggleButtonOnClick);
smallMode = true;
}
} else if (smallMode) {
myBtn.classList.remove('btn-small-screen');
myBtn.classList.remove('btn-clicked');
myBtn.removeEventListener('click', toggleButtonOnClick);
smallMode = false;
}
}

JS DOM manipulation 'mouseleave' triggers unexpectedly in Safari browser

EDIT: 'mouseleave' event is constantly being triggered, although the mouse does not leave the element.
Code works as intended in: chrome, mozilla, edge, opera. But not safari!
I have a vanilla JavaScript solution that changes images every 1000ms when mouse hovered on parent element. There can be any amount of images inside wrapper and this should still work. To be more clear, javascript adds "hidden" class for every image and removes it from the one who's turn is to be displayed. (Code is in fiddle).
In safari it seems to be stuck swapping 2-3rd image. Am I using wrong dom-manipulation approach? How can I find the error?
Problem presentation: https://jsfiddle.net/pcwudrmc/65236/
let imageInt = 0;
let timeOut;
let imagesWrapper = document.querySelectorAll('.items-box__item');
// Events for when mouse enters/leaves
imagesWrapper.forEach(el => {
el.addEventListener('mouseenter', () => startAnim(el));
el.addEventListener('mouseleave', () => stopanim(el));
});
// DOM Manipulation functions
function changeImages(el) {
imageInt += 1;
if (imageInt === el.children[0].children.length) {
// reset to 0 after going through all images
imageInt = 0;
}
for (let i = 0; i < el.children[0].children.length; i++) {
// Adds "hidden" class to ALL of the images for a product
el.children[0].children[i].classList.add('hidden');
}
// Removes "hidden" class for one
el.children[0].children[imageInt].classList.remove('hidden');
// changeImage calls itself again after 1 second, if hovered
timeOut = setTimeout(changeImages.bind(null, el), 1000);
}
function changeBack(el) {
for (let i = 0; i < el.children[0].children.length; i++) {
// Adds "hidden" class to ALL of the images for a product
el.children[0].children[i].classList.add('hidden');
}
// Removes "hidden" class for the first image of the item
el.children[0].children[0].classList.remove('hidden');
}
startAnim = element => { changeImages(element) }
stopanim = element => {
changeBack(element);
clearTimeout(timeOut);
imageInt = 0;
}
.items-box__item {
width: 300px;
height: 300px;
}
.items-box__item--main-image {
object-fit: contain;
width: 90%;
height: 265px;
}
.hidden {
display: none;
}
<h3>Hover on pic and hold mouse</h3>
<div class="items-box__item">
<a href="/">
<img class="items-box__item--main-image" src="https://res.cloudinary.com/keystone-demo/image/upload/c_limit,h_300,w_300/v1525948251/yrllszgndxzlydbycewc.jpg"/>
<img class="items-box__item--main-image hidden" src="https://res.cloudinary.com/keystone-demo/image/upload/c_limit,h_300,w_300/v1525948251/e96i5zbvxxuxsdczbh9d.jpg"/>
<img class="items-box__item--main-image hidden" src="https://res.cloudinary.com/keystone-demo/image/upload/c_limit,h_300,w_300/v1525948252/boaqfs3yuc4r7mvhsqqu.jpg"/>
</a>
</div>
You need to look at relatedTarget of mouseleave event, as both mouseenter and mouseleave happen every time the displayed image changes.
Also your code might be simplified. See the snippet below. Hope it helps.
const play = (box) => {
while (!box.classList.contains('items-box__item')) box = box.parentElement;
var img = box.querySelector('.show');
img.classList.remove('show');
(img.nextElementSibling || box.firstElementChild).classList.add('show');
}
const stop = ({target: box, relatedTarget: rt}) => {
while (!box.classList.contains('items-box__item')) box = box.parentElement;
while (rt != box && rt) rt = rt.parentElement;
if (rt === box) return;
box.querySelector('.show').classList.remove('show');
box.firstElementChild.classList.add('show');
box.play = clearInterval(box.play);
}
[...document.querySelectorAll('.items-box__item')]
.forEach((box) => {
box.addEventListener(
'mouseenter',
function() {
if (box.play) return;
play(box);
box.play = setInterval(() => play(box), 1000);
}
);
box.addEventListener('mouseleave', stop);
});
.items-box__item {
display: block;
width: 300px;
height: 300px;
}
.items-box__item img {
object-fit: contain;
width: 90%;
height: 265px;
display: none;
}
img.show {
display: initial
}
<h3>Hover on pic and hold mouse</h3>
<a class="items-box__item" href="/">
<img class="show" src="https://res.cloudinary.com/keystone-demo/image/upload/c_limit,h_300,w_300/v1525948251/yrllszgndxzlydbycewc.jpg">
<img src="https://res.cloudinary.com/keystone-demo/image/upload/c_limit,h_300,w_300/v1525948251/e96i5zbvxxuxsdczbh9d.jpg">
<img src="https://res.cloudinary.com/keystone-demo/image/upload/c_limit,h_300,w_300/v1525948252/boaqfs3yuc4r7mvhsqqu.jpg">
</a>

Categories