Several Intersection Observers tracking different elements merged into a single one - javascript

I have a simple code using the IntersectionObserver, that basically tells when an object is visible on the screen, when that happens I want a title to change to the same name of that visible element. The thing is that I've created 5 different variables for each observer related to each of the 5 upcoming areas. Is there a way to simplify this code?
Right now it works but seems to be wrong to me and I cannot figure out how to merge them all.
Thanks in advance!!
var observer = new IntersectionObserver(function(entries) {
if(entries[0].isIntersecting === true)
document.querySelector(".productTitle").innerHTML = "Logotype";
}, { threshold: [1] });
var observer1 = new IntersectionObserver(function(entries) {
if(entries[0].isIntersecting === true)
document.querySelector(".productTitle").innerHTML = "Branding";
}, { threshold: [1] });
var observer2 = new IntersectionObserver(function(entries) {
if(entries[0].isIntersecting === true)
document.querySelector(".productTitle").innerHTML = "Website Dev.";
}, { threshold: [1] });
var observer3 = new IntersectionObserver(function(entries) {
if(entries[0].isIntersecting === true)
document.querySelector(".productTitle").innerHTML = "3d Modeling";
}, { threshold: [1] });
var observer4 = new IntersectionObserver(function(entries) {
if(entries[0].isIntersecting === true)
document.querySelector(".productTitle").innerHTML = "Vectorial Work";
}, { threshold: [1] });
observer.observe(document.querySelector("#visibleLogo"));
observer1.observe(document.querySelector("#visibleBranding"));
observer2.observe(document.querySelector("#visibleWeb"));
observer3.observe(document.querySelector("#visible3d"));
observer4.observe(document.querySelector("#visibleVector"));

Assign a common class like .page to your articles Elements
You need only one IntersectionObserver instance, and you can attach it to all the desired .page inside a.forEach().
Use data-* attribute to store the desired title in the HTML itself like i.e: data-title="Title to show when in viewport"
const elTitle = document.querySelector(".productTitle");
const pageInViewport = (entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
elTitle.textContent = entry.target.dataset.title;
}
});
};
const pageObs = new IntersectionObserver(pageInViewport);
const obsOptions = {threshold: [1]};
// Attach observer to every .page element:
document.querySelectorAll('.page')
.forEach(EL => pageObs.observe(EL, obsOptions));
* {
margin: 0;
box-sizing: border-box;
}
.productTitle {
position: fixed;
top: 0;
background: gold;
}
.page {
padding: 30px;
border: 1px solid #000;
height: 100vh;
}
<div class="productTitle"></div>
<div class="page" id="visibleLogo" data-title="Logotype">visibleLogo</div>
<div class="page" id="visibleBranding" data-title="Branding">visibleBranding</div>
<div class="page" id="visibleWeb" data-title="Website Dev.">visibleWeb</div>
<div class="page" id="visible3d" data-title="3d Modeling">visible3d</div>
<div class="page" id="visibleVector" data-title="Vectorial Work">visibleVector</div>

Related

How to modify the code to not get appendchild error

I have been trying to use the below code but whenever I am trying to run it in react file I am getting this error
"Uncaught TypeError: Cannot read properties of null (reading 'appendChild')" error .
At first I found a design from codepen, then I tried to integrate it in react code but error is enevitable
Thus I am in need of finding answer to it
const container = document.getElementById('container');
const circlesArr = [];
for(let i=0; i<15; i++) {
circlesArr[i] = [];
for(let j=0; j<15; j++) {
const circle = document.createElement('div');
circle.classList.add('circle');
container.appendChild(circle);
circlesArr[i].push(circle);
}
}
function growCircles(i, j) {
if(circlesArr[i] && circlesArr[i][j]) {
if(!circlesArr[i][j].classList.contains('grow')) {
circlesArr[i][j].classList.add('grow');
setTimeout(() => {
growCircles(i-1, j)
growCircles(i+1, j)
}, 100)
setTimeout(() => {
circlesArr[i][j].classList.remove('grow');
}, 300);
}
}
}
circlesArr.forEach((15, i) => {
cols.forEach((circle, j) => {
circle.addEventListener('click', () => {
growCircles(i, j);
});
});
});
return (<div id="container" className="container"></div>)}
The CSS code
.container {
display: flex;
flex-wrap: wrap;
width: 450px;
}
.circle {
background-color: #5295F1;
height: 14px;
width: 14px;
transition: transform 0.3s linear;
}
.circle.grow {
transform: scale(2);
}
I assume the code before return is inside your component's body. It means you are trying to get an element with id "container" before rendering it so at that point the document.getElementById('container') returns null which doesn't have appendChild method.

How could I restart this current function

During the use of this function you click a button that will give you a square that will display a colour within a square. However when you click the button 60 times the programme stops working because all the colours have been used. How would I then restart this process so that I can continue clicking ?
<html>
<head>
<script>
var usedColors = [];
function randomColour(){
var colour=[];
colour[0]= '#edf2fb';
colour[1]= '#d7e3fc';
colour[3]= '#c1d3fe';
colour[4]= '#d1d1d1';
colour[5]= '#e1dbd6';
colour[6]= '#e2e2e2';
colour[7]= '#f9f6f2';
colour[8]='#ffc09f';
colour[9]='#ffee93';
colour[10]='#fcf5c7';
colour[11]='#a0ced9';
colour[12]='#adf7b6';
colour[13]='#809bce';
colour[14]='#95b8d1';
colour[15]='#b8e0d2';
colour[16]='#d6eadf';
colour[17]='#eac4d5';
colour[18]='#e8d1c5';
colour[19]='#eddcd2';
colour[20]='#fff1e6';
colour[21]='#f0efeb';
colour[22]='#eeddd3';
colour[23]='#e8dff5';
colour[24]='#fce1e4';
colour[25]='#fcf4dd';
colour[26]='#ddedea';
colour[27]='#daeaf6';
colour[28]='#d3ab9e';
colour[29]='#eac9c1';
colour[30]='#ebd8d0';
colour[31]='#ffe5ec';
colour[32]='#ffc2d1';
colour[33]='#ceb5b7';
colour[35]='#b5d6d6';
colour[36]='#f2f5ff';
colour[37]='#efcfe3';
colour[38]='#eaf2d7';
colour[39]='#b3dee2';
colour[40]='#f8ad9d';
colour[41]='#fbc4ab';
colour[42]='#ffdab9';
colour[43]='#cdb4db';
colour[44]='#ffc8dd';
colour[45]='#ffafcc';
colour[46]='#bde0fe';
colour[47]='#a2d2ff';
colour[48]='#fdffb6';
colour[49]='#caffbf';
colour[50]='#9bf6ff';
colour[51]='#a0c4ff';
colour[52]='#ffc6ff';
colour[53]='#a7bed3';
colour[54]='#c6e2e9';
colour[55]='#f1ffc4';
colour[56]='#ffcaaf';
colour[57]='#dab894';
colour[58]='#fec7bc';
colour[59]='#fcf5ee';
var pick= Math.floor(Math.random()*60);
if(usedColors.includes(pick)){
randomColour();
}
usedColors.push(pick); document.getElementById("colorpad").style.backgroundColor = colour[pick];
console.log(usedColors);
}
</script>
</head>
<body>
<div id="colorpad" style="height: 300px; width: 300px;">
<button onclick="randomColour()">btn</button>
</div>
</body>
</html>
Keep track of the current color:
let curr = 0;
On "Next" button click increment the curr index, and loopback with the help of the Modulo Operator %:
curr += 1;
curr %= colour.length; // On "next" loop back to 0 if we reached the end
Finally there's your color back at 0
console.log(colour[curr]); // '#edf2fb'
Demonstration:
const EL = (sel, EL) => (EL||document).querySelector(sel);
const colors = [
'#edf2fb',
'#d7e3fc',
'#c1d3fe',
'#d1d1d1',
'#e1dbd6',
'#e2e2e2',
'#f9f6f2',
'#ffc09f',
'#ffee93',
'#fcf5c7',
];
const tot = colors.length;
let curr = 0;
const curr_rand = () => curr = Math.floor(Math.random() * tot);
const curr_next = () => (curr += 1, curr %= tot);
const applyColor = () => EL("body").style.background = colors[curr];
EL("#rand").addEventListener("click", () => {
curr_rand();
applyColor();
console.log(curr);
});
EL("#next").addEventListener("click", () => {
curr_next();
applyColor();
console.log(curr);
});
<button type="button" id="rand">Generate</button>
<button type="button" id="next">Next</button>
Here's a snippet using a small factory function to be able to recolor infinitely. The recoloring is done using a randomly shuffled copy of the color array. This way you don't have to check each time if a color is already used.
Furthermore, see comments in the snippet. It uses event delegation for the handler, because it's generally not a good idea to use inline event handlers.
To keep the snippet lean, only 10 colors are used.
const colorize = randomColor(
['#edf2fb', '#d7e3fc', '#c1d3fe', '#d1d1d1', '#e1dbd6',
'#e2e2e2', '#f9f6f2', '#ffc09f', '#ffee93', '#fcf5c7'] );
document.addEventListener(`click`, evt => {
if (evt.target.id === `colorChange`) {
return colorize(document.querySelector(`.color`));
}
});
// randomColor is a factory function, it returns a function.
// The function can use the inner variables as well as the
// [colors] array from the parameter(they are 'closed over').
// The [colors] array is shuffled randomly (Fisher-Yates) and
// on every call (from click) the first color from that shuffled
// array is picked - until it's empty. If [shuffled] is empty
// the original [color] array is reshuffled (into [shuffled]).
// This way the coloring is restarted with a new set of
// random colors.
function randomColor(colors){
const shuffle = array =>
[...Array(array.length)]
.map((el, i) => Math.floor(Math.random() * i))
.reduce( (a, rv, i) =>
([a[i], a[rv]] = [a[rv], a[i]]) && a, array.slice());
// ^ slice copies the original
let shuffled = shuffle(colors);
// return a function
return colorDiv => {
const restarted = !shuffled.length;
shuffled = shuffled.length > 0 ? shuffled : shuffle(colors);
const nwColor = shuffled.shift();
colorDiv.style.backgroundColor = nwColor;
// for demo: color is displayed, and bold/red if [shuffled] is renewed
colorDiv.classList[restarted ? `add` : `remove`](`restart`);
colorDiv.dataset.color = nwColor;
}
}
.color {
width: 100px;
height: 100px;
border: 1px solid #777;
margin: 1rem 0;
text-align: center;
line-height: 100px;
}
.color:before {
content: attr(data-color);
}
.color.restart:before {
color: red;
font-weight: bold;
}
<div class="color"></div>
<button id="colorChange">change</button>

Add a blinking effect to single character of array

I am trying to achieve blinking effect to a single character of an array.
For example, if "text" get load inside paragraph <p> tag or container then first character of text should blink and when user types the blinking character in input area blinking effect must move to blink on next character.
I need assistance in solving this problem. Any instructions or help will be so grateful.
Here what I've tried so far:
let displayElem = document.getElementById("me");
const inputElem = document.getElementById("input");
const text = "Hey It's bad day, not a bad life,you'll be okay...!"
text.split('').forEach(char => {
const chrspan = document.createElement('span')
chrspan.innerText = char;
displayElem.appendChild(chrspan);
});
inputElem.addEventListener('input', () => {
var vl = document.getElementById("input").value;
const arrayq = displayElem.querySelectorAll('span')
const arrayv = inputElem.value
let correct = true;
arrayq.forEach((chSpan, index) => {
const char = arrayv[index];
if (char == null) {
correct = false;
} else if (char === chSpan.innerText) {
chSpan.classList.add('blink-bg')
} else {
chSpan.classList.remove('blink-bg')
correct = false
}
})
})
.blink-bg {
text-align: center;
color: #fff;
display: inline-block;
border-radius: 3px;
animation: blinkingBackground 1s infinite;
}
#keyframes blinkingBackground {
from { background-color: #f1ebeb; }
to { background-color: #080808; }
}
<p id="me"></p>
<input id="input" type="input" />
let displayElem = document.getElementById("me");
const inputElem = document.getElementById("input");
const text = "Hey It's bad day, not a bad life,you'll be okay...!"
text.split('').forEach(char => {
const chrspan = document.createElement('span')
chrspan.innerText = char;
displayElem.appendChild(chrspan);
});
inputElem.addEventListener('input', () => {
var vl = document.getElementById("input").value;
const arrayq = displayElem.querySelectorAll('span')
const arrayv = inputElem.value
let correct = true;
arrayq.forEach((chSpan, index) => {
const char = arrayv[index];
if (char == null) {
correct = false;
} else if (char === chSpan.innerText) {
chSpan.classList.add('blink-bg')
// document.getElementById("p_id").innerHTML = chSpan.innerText;
} else {
chSpan.classList.remove('blink-bg')
correct = false
}
})
})
This might be a good starting point for you. I changed a couple things,
I made a helper function $ to grab items from the dom as a personal prefrence.
I created an array of all the spans on the document, making it easier to keep track of which element has the blinking class. I created a helper function to grab what the activeText is from the txtArr instead of checking the text content. This way I can avoid using the rendered screen as a storage area for information, and instead have the screen mirror what is happening in my js.
On input I check the last character entered, and if it is the character that is blinking, I increment increment which span is blinking.
This is meant to be a simple demo of how to accomplish this task, you may want to have different functionality, but hopefully this helps as a starting point!
const $ = str => [...document.querySelectorAll(str)];
let displayElem = $("#me")[0];
const inputElem = $("#input")[0];
const text = "Hey! It's a bad day, not a bad life, you'll be okay...!"
const txtArr = [...text];
const txtSpans = txtArr.map(char => {
const span = document.createElement("span");
span.innerText = char;
return span;
});
let activeIndex = -1;
const activeText = () => txtArr[activeIndex];
function renderSpans() {
displayElem.innerHtml = "";
txtSpans.forEach(span => displayElem.appendChild(span));
};
function updateActive() {
const firstRun = activeIndex == -1;
if (!firstRun)
txtSpans[activeIndex].classList.remove("blink-bg");
activeIndex++;
if (activeIndex == txtSpans.length) return;
txtSpans[activeIndex].classList.add("blink-bg");
}
updateActive();
renderSpans();
inputElem.addEventListener('input', e => {
const val = e.target.value;
if (val == "") return;
const lastChar = val[val.length - 1];
if (lastChar == activeText()) updateActive();
})
.blink-bg {
text-align: center;
color: #fff;
display: inline-block;
border-radius: 3px;
animation: blinkingBackground 1s infinite;
}
#keyframes blinkingBackground {
from { background-color: #f1ebeb; }
to { background-color: #080808; }
}
<p id="me"></p>
<input id="input" type="input" />

Javascript - How to make a section 'active' when scrolled to

I'm very new to JavaScript so apologies if I'm lacking clarity in any of my description.
I've built a small website that has a java-script generated menu and I'm just wanting to add functionality to highlight a section (and make 'active') in the menu when scrolled to. I've put together the following, which isn't throwing any errors in the console, so I'm unsure what I've missed:
const sectionHead = document.querySelectorAll('h2');
const sections = document.querySelectorAll('section');
const nav = document.querySelector('nav');
// build the nav
function navMenu(){
for(let section of sectionHead){
let listItem = document.createElement("li");
listItem.innerHTML = section.textContent;
nav.appendChild(listItem);
listItem.classList.add('menu__link');
listItem.addEventListener("click", ()=>{
section.scrollIntoView({behavior: "smooth"});
});
};
}
navMenu();
const nav_items = document.querySelectorAll('.menu__link')
window.addEventListener("scroll", () => {
let current = "";
sections.forEach((section) => {
const sectionTop = section.offsetTop;
const sectionHeight = section.clientHeight;
if (pageYOffset >= sectionTop - sectionHeight / 3) {
current = section.getAttribute("id");
}
});
//set section as active
nav_items.forEach((li) => {
li.classList.remove("your-active-class");
section.classList.remove("your-active-class");
if (section.classList.contains(current)) {
section.classList.add("your-active-class");
//console.log (li.classList);
}
});
});
The 'your-active-class' class has some custom CSS setup so it will just change visibility in the menu.
Any help is really appreciated
This is how you would observe whether a DOM Element is within view, and apply/remove a classname to it. If you look at your inspector, you'll see the 'active' class being added/removed when they enter and exit view.
window.addEventListener("load", function() {
//Query objects on document load
const sections = document.querySelectorAll("section")
console.log(`We have ${sections.length} sections`)
// Create options for Observer:
const options = {
rootMargin: '100px 0px',
threshold: [0.25, 0, 0.25, 0]
}
// Create instance of IO:
let observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.intersectionRatio > 0) {
entry.target.classList.add('active')
} else {
entry.target.classList.remove('active')
}
})
}, options)
// Iterate over each queried el, and add observer:
sections.forEach(section => {
observer.observe(section)
})
})
section {
background-color: red;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 6rem auto;
transition: all 300ms ease-in-out;
}
.active {
background-color: rgba(0,255,0,0.3);
transition: all 300ms ease-in-out;
}
<section><div>This is the first section</div></section>
<section><div>This is the second</div></section>
<section><div>This is the third</div></section>

How to force that specific div draggable could not be outside from parent div

I'm developing a draggable vertical list to drag and drop rows using react.
I've created a div with a class name called placeholder, this specific div is used to create a space between other divs where it will drop.
My problem is that this specific div,
how can it be draggable, can be dragged out of the div that contains it. And show me the next error because it's outside:
NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
How can make that this specific div can not be outside from the div has been created??
Here is the code:
import React, { Component } from 'react';
import Item from '../Item';
import './list.scss';
let placeholder = document.createElement("div");
placeholder.className = "placeholder";
class DraggableVerticalList extends Component {
constructor(props) {
super(props);
this.state = {...props};
}
dragStart = (e) => {
this.dragged = e.currentTarget;
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', this.dragged);
};
dragEnd = (e) => {
this.dragged.style.display = 'block';
this.dragged.parentNode.removeChild(placeholder);
// update state
var data = this.state.colors;
var from = Number(this.dragged.dataset.id);
var to = Number(this.over.dataset.id);
if(from < to) to--;
data.splice(to, 0, data.splice(from, 1)[0]);
this.setState({ colors: data });
};
dragOver = (e) => {
e.preventDefault();
this.dragged.style.display = "none";
if(e.target.className === 'placeholder') return;
this.over = e.target;
e.target.parentNode.insertBefore(placeholder, e.target);
};
render() {
return (
<div className="draggable-container" onDragOver={this.dragOver.bind(this)}>
{ this.state.colors.map((item, i) => {
return (
<div className="item" data-id={i} key={i} draggable='true' onDragEnd={this.dragEnd.bind(this)} onDragStart={this.dragStart.bind(this)}>
<Item item={item} key={i} />
</div>
)})}
</div>
);
}
}
export default DraggableVerticalList;
His css file:
.draggable-container {
width: 100%;
height: 100%;
padding: 10px 10px;
background-color: rgb(233, 201, 201);
border-radius: 10px;
.draggable-list {
padding: 5px;
}
}
.placeholder {
background: rgb(255,240,120);
width: 100%;
height: 40px;
&:before {
content: "Drop here";
color: rgb(225,210,90);
}
}
SOLVED:
dragOver = (e) => {
e.preventDefault();
/* With this line div cant be outside */
if (e.target.className === 'draggable-container') return;
this.dragged.style.display = "none";
if(e.target.className === 'placeholder') return;
this.over = e.target;
e.target.parentNode.insertBefore(placeholder, e.target);
};

Categories