My goal I want to run loop that decrements a global variable stepwise in n ms (for Example: 200ms) time intervals.
Thanks in advance!
What i already tried
I tried to use ascy await. But in combination with css transition i run in an infinite loop (In codepen.io). But here in SO you will see that it starts not running smoothly if you keep pressing arrow up.
const procentage = document.querySelector(".procentage");
const green = engine.querySelector(".green");
let number = 0;
let decrementing = false;
window.addEventListener('keydown', (e) => {
e = e || window.event;
e.preventDefault();
if (e.keyCode == '38') {
console.log("accelerate");
actionHandler( number++ );
decrementing = false;
downLoop();
}
});
function actionHandler(num) {
procentage.innerHTML = num;
const str = num + "%"
green.style.width = str;
procentage.innerHTML = str;
}
window.addEventListener('keyup', (e) => {
e = e || window.event;
e.preventDefault();
if (e.keyCode == '38') {
console.log("decelerate");
decrementing = true;
downLoop();
}
});
async function downLoop() {
if (! decrementing) {
return false
};
const timer = ms => new Promise(res => setTimeout(res, ms));
while (number > 1) {
// how to decrement ever 200ms???
actionHandler( number-- );
await timer(200)
}
}
#engine {
background-color:black;
height: 50px;
position: relative;
}
p {
text-align: center;
}
.green {
background:green;
height: 50px;
width:0%;
transition: width 0.2s;
text-align:center;
}
.procentage {
position:absolute;
top: 50%;
left: 50%;
transform: translate(0%,-50%);
color: white;
fon-weight: bold;
font-size:28px;
}
<div id="engine">
<div><span class="procentage">0</span></div>
<div class="green"></div>
</div>
<p>press arrow Up</p>
Whenever you animate, you shouldn't rely on setInterval or setTimeout, because that means that you will update "somewhere after X milliseconds" which will often end up in the middle of a screen repaint, and will therefor cause janky animation.
Instead, you should use RequestAnimationFrame which does a calculation before every repaint. So if you got a monitor with a framerate of 60 Hz, that means that you will do 60 repaints every second. For each repaint, check if enough time have passed since the last update (shouldTriggerUpdate() below) and then check if you should update the number.
I also added the class KeyHandler to keep track of which keys that have been pressed.
I got sloppy at the end and just added a decrement as an "else" of the if statement. You will figure something out when you get there when you want to set up more keys to be pressed.
You shouldn't use KeyboardEvent.keyCode, but instead KeyboardEvent.code.
const procentage = document.querySelector(".procentage");
const green = engine.querySelector(".green");
let number = 0;
let speed = 200 // ms
let lastUpdated = 0; // ms
let animationId = 0; // use later on to pause the animation
class KeyHandler {
ArrowLeft = false
ArrowUp = false
ArrowRight = false
ArrowDown = false
#setKey(code, value) { // private method
if (typeof this[code] != undefined) {
this[code] = value;
}
}
set pressedKey(code) {
this.#setKey(code, true);
}
set releasedKey(code) {
this.#setKey(code, false);
}
}
let keyHandler = new KeyHandler();
window.addEventListener('keydown', (e) => {
e = e || window.event;
e.preventDefault();
keyHandler.pressedKey = e.code;
});
window.addEventListener('keyup', (e) => {
e.preventDefault();
keyHandler.releasedKey = e.code
});
function actionHandler(num) {
const str = num + "%"
green.style.width = str;
procentage.innerHTML = str;
}
function shouldTriggerUpdate(timeInMillis) {
let difference = timeInMillis - lastUpdated;
return difference >= speed;
}
function planeAnimation() {
let timeInMillis = new Date().getTime();
if (shouldTriggerUpdate(timeInMillis)) {
lastUpdated = timeInMillis;
if (keyHandler.ArrowUp) {
actionHandler(++number)
} else if (number > 0) {
actionHandler(--number)
}
}
animationId = requestAnimationFrame(planeAnimation)
}
animationId = requestAnimationFrame(planeAnimation);
#engine {
background-color: black;
height: 50px;
position: relative;
}
p {
text-align: center;
}
.green {
background: green;
height: 50px;
width: 0%;
transition: width 0.2s;
text-align: center;
}
.procentage {
position: absolute;
top: 50%;
left: 50%;
transform: translate(0%, -50%);
color: white;
fon-weight: bold;
font-size: 28px;
}
<div id="engine">
<div><span class="procentage">0</span></div>
<div class="green"></div>
</div>
<p>press arrow up</p>
From the above comments ...
"Instead of incrementing each time the number value push a new async timer function, set to 200 msec delay but not immediately triggered, into an array. Create an async generator from it and iterate over the latter via the for-await...of statement where one could decrement number again." – Peter Seliger
"#PeterSeliger Hi Peter! Thank you for your comment. Can you make a small example please?" – Maik Lowrey
And here the requested demonstration.
function createWait(delay) {
return async function wait () {
let settle;
const promise = new Promise((resolve) => { settle = resolve;});
setTimeout(settle, delay, { delay, state: 'ok' });
return promise;
};
}
async function* getWaitIterables(list) {
let wait;
while (wait = list.shift()) {
yield wait();
}
}
// demo for ...
// - creating an async `wait` function
// or a list of such kind.
// - creating an async generator from
// a list of async `wait` functions.
// - iterating an async generator of
// async `wait` functions.
const waitingList = [ // const waitingList = [];
2000, // waitingList.push(createWait(2000));
1000, // waitingList.push(createWait(1000));
3000, // waitingList.push(createWait(3000));
].map(createWait); // - The OP of cause needs to push into.
let number = 3; // - The incremented `number` value e.g. ... 3.
(async () => {
for await (const { delay, state } of getWaitIterables(waitingList)) {
--number;
console.log({ number, delay, state });
}
})();
console.log('... running ...', { number });
.as-console-wrapper { min-height: 100%!important; top: 0; }
Related
This is a function where you click on a character and it heals them by the amount specified in the switch. I need the listener to trigger only one time per function call but the removal doesn't seem to be doing anything. As in you can just keep clicking and it keeps going through the switch. I've tested and ensured that the function itself is only being called once so I have no idea what's going on here. Tried removing the listener like document.removeEventListener('click', addAllyTargets) as well, no change. Angels_Grace_Part2() is purely imagery/text and has nothing to do with the listener.
var ally_targets = []
//make the list of targets
function makeAllyTargets() {
let maketargets = document.getElementsByClassName('ally_img')
for (let i = 0; i < maketargets.length; i++) {
ally_targets.push(maketargets[i])
};
};
var amt_healed;
function Angels_Grace() { //moderate healing spell on one ally
makeAllyTargets();
for (let i = 0; i < ally_targets.length; i++) {
//add the listener to each target
ally_targets[i].addEventListener('click', function addAllyTargets() {
//amt healed is 55% of the target's max.
const selected_ally = ally_targets.indexOf(this);
switch (selected_ally) {
case 0: //knight
amt_healed = 303;
//ensure it doesn't go over max
if (warrior_hp.value + amt_healed > 550) {
Angels_Grace_Part2()
warrior_hp.value = 550;
} else {
Angels_Grace_Part2()
warrior_hp.value += amt_healed;
};
//remove the listener
ally_targets[i].removeEventListener('click', addAllyTargets)
break;
//the other cases follow the same logic
default:
console.log("heal switch - shits fucked")
break;
};
});
};
};
I went overboard in my answer.
There were a lot of things going on in your code that, in my opinion, could have been made simpler.
And so rather than go into a deep explanation as to why, I offer this for your consideration.
function AngelsGrace(event) {
//amt healed is 55% of the target's max.
let ds = event.target.dataset;
let hp = +ds.hp;
let maxHp = +ds.maxHp;
let healingRate = +ds.healingRate;
let amtHealed = maxHp * healingRate;
let excessiveHealing = hp + amtHealed > maxHp;
if (excessiveHealing)
amtHealed = maxHp - hp;
ds.hp = hp + amtHealed;
if (amtHealed) {
console.log(`${ds.class} hp was ${hp}. Healed for ${amtHealed}, now ${ds.hp}/${maxHp}`);
} else {
console.log(`${ds.class} is at Full Health`);
}
console.log("casting Angels_Grace_Part2()");
event.target.nextElementSibling.classList.toggle("angels-grace");
}
function CastAngelsGrace() { //moderate healing spell on one ally
var oneTimeOnly = {
once: true
};
let htmlCollection = document.getElementsByClassName('ally_img');
for (const tgt of htmlCollection) {
tgt.addEventListener('click', AngelsGrace, oneTimeOnly)
tgt.nextElementSibling.classList.toggle("angels-grace");
};
}
.character {
display: inline-block;
position: relative;
height: 100px;
width: 100px;
border: 1px solid grey;
margin: 10px;
}
.ally_img {
height: 70px;
width: 70px;
}
.spell-container {
position: absolute;
top: 0;
right: 0;
}
.spell-container:after {
opacity: 0;
}
.spell-container.angels-grace:after {
content: "AG";
opacity: 1;
}
<div class="character">
<image src="https://picsum.photos/id/718/200" class="ally_img"
data-class="knight"
data-healing-class="warrior"
data-healing-rate="0.55"
data-max-hp="1000"
data-hp="200" />
<span class="spell-container"></span>
</div>
<div class="character">
<image src="https://picsum.photos/id/237/200" class="ally_img"
data-class="rogue"
data-healing-class="sneaky"
data-healing-rate="0.35"
data-max-hp="500"
data-hp="100" />
<span class="spell-container"></span>
</div>
<button onclick="CastAngelsGrace()">Cast Angel Grace</button>
There are some components stacked on top of each other and the last component has a timer. I want the timer to start only when that component is visible on screen or when scroll is reached to that component. [REPL]
let count_val = 80;
let count = 0;
function startTimer() {
let count_interval = setInterval(() => {
count += 1;
if(count >= count_val) {
clearInterval(count_interval);
}
}, 100);
}
// check if scroll reached to component and run below function.
startTimer();
How do I achieve this?
Like commented this can be achieved using Intersection Observer and an action
REPL
<script>
let count_val = 80;
let count = 0;
function timer(node) {
let interval
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if(entry.isIntersecting) {
interval = setInterval(() => {
count += 1;
if(count === count_val) {
clearInterval(interval);
}
}, 100);
} else {
clearInterval(interval)
count = 0
}
})
})
observer.observe(node)
return {
destroy: () => observer.disconnect()
}
}
</script>
<div use:timer>
<p>
Counter - {count}
</p>
</div>
<style>
div {
height: 100vh;
display: grid;
place-items: center;
background-color: teal;
color: #0a0a0a;
font-size: 4rem;
}
</style>
I want to change the colour of a <main> element using the wheel event. If the event.deltaY is negative i.e scrolling up I want to loop through the array backwards, so, if index = 2 it would go blue, purple, black, white and then to the end, so blue. And if the event.deltaY is positive i.e scrolling down, I want to loop through the array forwards, if index = 3, it would go blue, orange, white, black, etc. It should keep looping infinitely and either way whenever there is a scroll. Currently, I'm having trouble setting the index to loop at either end. Any help would be greatly appreciated.
const container = document.querySelector('main')
const classes = ['white', 'black', 'purple', 'blue', 'orange']
let index = 0
const throttle = (func, limit) => {
let inThrottle
return function() {
const args = arguments
const context = this
if (!inThrottle) {
func.apply(context, args)
inThrottle = true
setTimeout(() => inThrottle = false, limit)
}
}
}
function changeColour(event) {
if (event.deltaY < 0) {
++index
if (index === index.length - 1) {
index = 0
}
console.log(index);
container.classList = classes[index]
}
if (event.deltaY > 0) {
--index
console.log(index);
if (index === 0) {
index = index.length - 1
}
container.classList = classes[index]
}
}
container.addEventListener('wheel', throttle(changeColour, 1250))
main {
height: 50px;
width: 50px;
border: 1px solid;
}
main.white {
background-color: #FFFFFF;
}
main.black {
background-color: #101010;
}
main.purple {
background-color: #8E002E;
}
main.blue {
background-color: #002091;
}
main.orange {
background-color: #C05026;
}
<main>
</main>
Issues
index = index.length - 1 index = classes.length -1
container.classList = classes[index] container.classList.add(classes[index])
There is no statement to remove any of the other classes.
Event listeners and on-event properties do not use the parentheses of a function because it will be misinterpreted as a direct call to the function to run. Callback functions are the opposite in that they wait to be called on. If an extra parameter is needed by the callback function you'll need to wrap the callback function with an anonymous function, here's the proper syntax:
on-event property: document.querySelector('main').onwheel = scrollClass;
event listener: document.querySelector('main').addEventListener('wheel', scrollClass)
on-event property passing extra parameters:
document.querySelector('main').onwheel = function(event) {
const colors = ['gold', 'black', 'tomato', 'cyan', 'fuchsia'];
scrollClass(event, colors);
}
event listener passing extra parameters:
document.querySelector('main').addEventListener('wheel', function(event) {
const colors = ['gold', 'black', 'tomato', 'cyan', 'fuchsia'];
scrollClass(event, colors);
}
Details commented in demo
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>Wheel Event Demo</title>
<style>
:root {
font: 400 16px/1.2 Consolas;
}
html,
body {
width: 100%;
height: 100%;
}
body {
overflow-y: scroll;
overflow-x: hidden;
padding: 5px 0 20px;
}
main {
height: 50vh;
width: 50vw;
margin: 10px auto;
border: 1px solid #000;
}
main.gold {
background-color: #FFCC33;
transition: 0.5s;
}
main.black {
background-color: #101010;
transition: 0.5s;
}
main.tomato {
background-color: #FF6347;
transition: 0.5s;
}
main.cyan {
background-color: #E0FFFF;
transition: 0.5s;
}
main.fuchsia {
background-color: #FD3F92;
transition: 0.5s;
}
</style>
</head>
<body>
<main></main>
<script>
// Reference the main tag
const container = document.querySelector('main');
// Declare index
let index = 0;
/** scrollClass(event, array)
//# Params: event [Event Object]: Default object needed for event properties
// array [Array].......: An array of strings representing classNames
//A Pass Event Object and array of classes
//B Reference the tag bound to the wheel event (ie 'main')
//C1 if the change of wheel deltaY (vertical) is less than 0 -- increment index
//C2 else if the deltaY is greater than 0 - decrement index
//C3 else it will just be index (no change)
//D1 if index is greater than the last index of array -- reset index to 0
//D2 else if index is less than zero -- reset index yo the last index of array
//D3 else index is unchanged.
//E Remove all classes of the currentTarget ('main')
//F Find the string located at the calculated index of the array and add it as a
className to currentTarget
*/
function scrollClass(event, array) { //A
const node = event.currentTarget; //B
index = event.deltaY < 0 ? ++index : event.deltaY > 0 ? --index : index; //C1-3
index = index > array.length - 1 ? 0 : index < 0 ? array.length - 1 : index; //D1-3
node.className = ''; //E
node.classList.add(array[index]); //F
}
/*
Register wheel event to main
When an event handler (ie scrollClass()) has more than the Event Object to pass
as a parameter, you need to wrap the event handler in an anonymous function.
Also, it's less error prone if the parameter is declared within the
anonymous function.
*/
container.addEventListener('wheel', function(event) {
const colors = ['gold', 'black', 'tomato', 'cyan', 'fuchsia'];
scrollClass(event, colors);
});
</script>
</body>
</html>
Modified code to include the corrections
const container = document.querySelector('main')
const classes = ['white', 'black', 'purple', 'blue', 'orange']
let index = 0
const throttle = (func, limit) => {
let inThrottle
return function() {
const args = arguments
const context = this
if (!inThrottle) {
func.apply(context, args)
inThrottle = true
setTimeout(() => inThrottle = false, limit)
}
}
}
function changeColour(event) {
if (event.deltaY < 0) {
++index
if (index >= classes.length) { // 👈 Here
index = 0
}
console.log(index);
container.classList = classes[index]
}
if (event.deltaY > 0) {
--index
console.log(index);
if (index <= -1) { // 👈 Here
index = classes.length - 1 // 👈 Here
}
container.classList = classes[index]
}
}
container.addEventListener('wheel', throttle(changeColour, 1250))
main {
height: 50px;
width: 50px;
border: 1px solid;
}
main.white {
background-color: #FFFFFF;
}
main.black {
background-color: #101010;
}
main.purple {
background-color: #8E002E;
}
main.blue {
background-color: #002091;
}
main.orange {
background-color: #C05026;
}
<main>
</main>
Below you will find a script I created for smooth scrolling when clicking local links. It is done via transform (no jQuery). As seen, I have implemented it using both inline CSS as well as external style sheets. I recommend the inline version, as it might be difficult to guess the index of the relevant style sheet.
The problem however, is that the actual movement of the scrollbar, happens after the transform is applied. Thus, if you click a link before scrolling transition is done, the code misbehaves.
Any thoughts on a solution to this?
EDIT:
I know there are jQuery solutions and third party polyfill libraries out there. My goal, however, was to recreate the jQuery functionality in plain vanilla JavaScript.
My Script:
// Get style rule's declaration block
function getStyleDeclaration(styleSheet, selectorText) {
const rules = styleSheet.cssRules;
return Array.from(rules).find(r => r.selectorText === selectorText).style;
// for (let i = 0; i < rules.length; i += 1) {
// if (rules[i].selectorText === selectorText) return rules[i].style;
// }
}
// Get specific style sheet, based on its title
// Many style sheets do not have a title however
// Which is main reason it is preferred to work with
// inline styles instead
function getStyleSheet(title) {
const styleSheets = document.styleSheets;
return Array.from(styleSheets).find(s => s.title === title);
// for (let i = 0; i < styleSheets.length; i += 1) {
// if (styleSheets[i].title === title) return styleSheets[i];
// }
}
function scrollToElement_ExternalStyleSheet(anchor, target) {
anchor.addEventListener("click", e => {
e.preventDefault();
const time = 1000;
// Distance from viewport to topof target
const distance = -target.getBoundingClientRect().top;
// Modify external style sheet
const transStyle = getStyleDeclaration(document.styleSheets[1], ".trans");
transStyle.transform = "translate(0, " + distance + "px)";
transStyle.transition = "transform " + time + "ms ease";
const root = document.documentElement; // <html> element
root.classList.add("trans");
window.setTimeout(() => {
root.classList.remove("trans");
root.scrollTo(0, -distance + window.pageYOffset);
}, time);
});
}
function scrollToElement_InlineStyle(anchor, target) {
const root = document.documentElement;
anchor.addEventListener('click', e => {
e.preventDefault();
const time = 900;
const distance = -target.getBoundingClientRect().top;
root.style.transform = 'translate(0, ' + distance + 'px)';
root.style.transition = 'transform ' + time + 'ms ease';
window.setTimeout(() => {
root.scrollTo(0, -distance + window.pageYOffset);
root.style.transform = null; // Revert to default
root.style.transition = null;
}, time);
});
}
function applySmoothScroll() {
const anchors = document.querySelectorAll("a");
const localAnchors = Array.from(anchors).filter(
a => a.getAttribute("href").indexOf("#") != -1
);
localAnchors.forEach(a => {
const targetString = a.getAttribute("href");
const target = document.querySelector(targetString);
// scrollToElement_ExternalStyleSheet(a, target);
scrollToElement_InlineStyle(a, target);
});
}
applySmoothScroll();
.box {
padding-bottom: 300px;
padding-top: 0.5rem;
background-color: orange;
text-align: center;
font-size: 200%;
}
.box:nth-child(even) {
background-color: lightblue;
color: white;
}
a {
color: black;
}
body {
margin: 0;
}
.trans {
transform: translate(0, -100px);
transition: transform 900ms ease;
}
<div id="s0" class="box">Click Me!</div>
<div id="s1" class="box">Click Me!</div>
<div id="s2" class="box">Click Me!</div>
Is there any way to force an update/run of an IntersectionObserver instance? The callback will be executed by default, when the viewport has changed. But I'm looking for a way to to execute it when other events happen, like a change of elements.
An Example:
On initialization everything works as expected. But when you change the position of the #red element, nothing happens.
// elements
let green = document.querySelector('#green');
let red = document.querySelector('#red');
// observer callback
let callback = entries => {
entries.forEach(entry => {
let isInside = entry.intersectionRatio >= 1 ? "fully" : "NOT";
console.log("#" + entry.target.id + " is " + isInside + " inside #container");
});
};
// start observer
let options = {root: document.querySelector('#container')};
let observer = new IntersectionObserver(callback, options);
observer.observe(green);
observer.observe(red);
// button action
document.querySelector('button').addEventListener('click', () => {
red.style.right = red.style.right == "" ? "0px" : "";
});
#container {
width: 100px;
height: 100px;
background: blue;
position: relative;
}
#green, #red {
width: 50px;
height: 50px;
background: green;
position: absolute;
}
#red {
background: red;
right: -10px;
}
<button>move #red</button>
<br /><br />
<div id="container">
<div id="green"></div>
<div id="red"></div>
</div>
Is there any way to make this working? Only thing that would work is to unobserve the element and start observing it again. This may be work for an single element, but not if the Observer has hundreds of elements to watch.
// elements
let green = document.querySelector('#green');
let red = document.querySelector('#red');
// observer callback
let callback = entries => {
entries.forEach(entry => {
let isInside = entry.intersectionRatio >= 1 ? "fully" : "NOT";
console.log("#" + entry.target.id + " is " + isInside + " inside #container");
});
};
// start observer
let options = {root: document.querySelector('#container')};
let observer = new IntersectionObserver(callback, options);
observer.observe(green);
observer.observe(red);
// button action
document.querySelector('button').addEventListener('click', () => {
red.style.right = red.style.right == "" ? "0px" : "";
observer.unobserve(red);
observer.observe(red);
});
#container {
width: 100px;
height: 100px;
background: blue;
position: relative;
}
#green, #red {
width: 50px;
height: 50px;
background: green;
position: absolute;
}
#red {
background: red;
right: -10px;
}
<button>move #red</button>
<br /><br />
<div id="container">
<div id="green"></div>
<div id="red"></div>
</div>
I don't think it is possible to force the intersection observer to update without calling unobserve/observe on the node, but you can do this for all observed nodes by saving them in a set:
class IntersectionObserverManager {
constructor(observer) {
this._observer = observer;
this._observedNodes = new Set();
}
observe(node) {
this._observedNodes.add(node);
this._observer.observe(node);
}
unobserve(node) {
this._observedNodes.remove(node);
this._observer.unobserve(node);
}
disconnect() {
this._observedNodes.clear();
this._observer.disconnect();
}
refresh() {
for (let node of this._observedNodes) {
this._observer.unobserve(node);
this._observer.observe(node);
}
}
}
Edit: use a Set instead of a WeakSet since they are iterable so there is no need to check if the element is being observed for each element in the body. Be carefull to call unobseve in order to avoid memory problems.
You just need to set threshold: 1.0 for your Intersection observer. This is a tricky parameter to comprehend. Threshold defines the percentage of the intersection at which the Observer should trigger the callback.
The default value is 0 which means callback will be triggered either when the very first or very last pixel of an element intersects a border of the capturing frame. Your element never completely leaves the capturing frame. This is why callback is never called.
If we set the threshold to 1 we tell the observer to trigger our callback when the element is 100% within the frame. It means the callback will be triggered on change in this state of 100% inclusiveness. I hope that sounds understandable :)
// elements
let green = document.querySelector('#green');
let red = document.querySelector('#red');
// observer callback
let callback = entries => {
entries.forEach(entry => {
let isInside = entry.intersectionRatio >= 1 ? "fully" : "NOT";
console.log("#" + entry.target.id + " is " + isInside + " inside #container");
});
};
// start observer
let options = {root: document.querySelector('#container'), threshold: 1.0 };
let observer = new IntersectionObserver(callback, options);
observer.observe(green);
observer.observe(red);
// button action
document.querySelector('button').addEventListener('click', () => {
red.style.right = red.style.right == "" ? "0px" : "";
});
#container {
width: 100px;
height: 100px;
background: blue;
position: relative;
}
#green, #red {
width: 50px;
height: 50px;
background: green;
position: absolute;
}
#red {
background: red;
right: -10px;
}
<button>move #red</button>
<br /><br />
<div id="container">
<div id="green"></div>
<div id="red"></div>
</div>
I may not get the question right, what I understand is you want to trigger the IntersectionObserver, so your callback get called. Why don't you call it directly?
document.querySelector('button').addEventListener('click', () => {
red.style.right = red.style.right == "" ? "0px" : "";
callback(red);
});
One way to do it is to use MutationObserver. If a mutation happens (in this case style change) call observe/unobserve on the element that has been changed. This way you don't have to do it for all elements.
Here is an example:
// observer callback
let callback = entries => {
entries.forEach(entry => {
let isInside = entry.intersectionRatio >= 1 ? "fully" : "NOT";
console.log("#" + entry.target.id + " is " + isInside + " inside #container");
});
};
// start observer
let options = {
root: document.querySelector('#container')
};
let observer = new IntersectionObserver(callback, options);
const boxes = document.querySelectorAll('#container > div');
boxes.forEach(box => {
observer.observe(box);
});
// button action
document.querySelector('button').addEventListener('click', () => {
red.style.right = red.style.right == "" ? "0px" : "";
});
// Mutation observer
const targetNode = document.querySelector('#container');
// Options for the observer (which mutations to observe). We only need style changes so we set attributes to true. Also, we are observing the children of container so subtree is true
const config = {
attributes: true,
childList: false,
subtree: true,
attributeFilter: ["style"]
};
// Callback function to execute when mutations are observed
const mutationCallback = (mutationsList, mutationObserver) => {
for (let mutation of mutationsList) {
if (mutation.type === 'attributes') {
console.log('The ' + mutation.attributeName + ' attribute was modified.');
observer.unobserve(mutation.target);
observer.observe(mutation.target);
}
}
};
// Create an observer instance linked to the callback function
const mutationObserver = new MutationObserver(mutationCallback);
// Start observing the target node for configured mutations
mutationObserver.observe(targetNode, config);
#container {
width: 300px;
height: 300px;
background: lightblue;
position: relative;
}
#green,
#red {
width: 100px;
height: 100px;
background: green;
position: absolute;
}
#red {
background: purple;
right: -10px;
}
<button>move #red</button>
<br /><br />
<div id="container">
<div id="green"></div>
<div id="red"></div>
</div>