I am playing around with intersection observer to create an infinite scroll dog website. As you scroll and 6 dogs appear, an api fires off 6 more times to grab more dogs to add to the DOM. I would like for the dogs to load in as a user scrolls but as an already viewed dog leaves the viewport and goes up on the page, that element is then deleted off the page. SO the dogs always load in scrolling down, but scrolling up you are always at the top of the page. My current implementation in the function called lastFunc is causing it to act really weird. How can I achieve the desired effect.
class CardGenerator {
constructor() {
this.$cardContainer = document.querySelector('.card-container');
this.$allCards = undefined;
this.observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
entry.target.classList.toggle('show', entry.isIntersecting);
if (entry.isIntersecting) {
this.observer.unobserve(entry.target);
}
});
},
{
threshold: 1,
rootMargin: '150px',
}
);
this.loadNewCards();
}
cacheDOMElements() {
this.$allCards = document.querySelectorAll('.card');
}
loadNewCards() {
for (let index = 0; index < 6; index++) {
fetch('https://dog.ceo/api/breeds/image/random', { method: 'GET' })
.then((result) => {
return result.json();
})
.then((r) => {
console.log(r);
const card = document.createElement('div');
card.classList.add('card');
const imageElement = document.createElement('img');
imageElement.classList.add('forza-img');
imageElement.setAttribute('src', r.message);
card.appendChild(imageElement);
this.observer.observe(card);
this.$cardContainer.append(card);
this.cacheDOMElements();
if (this.$allCards.length % 6 === 0) this.lastFunc();
});
}
}
lastFunc() {
console.log(this.$allCards);
if (this.$allCards.length > 12) {
this.$allCards.forEach((item, idx) => {
if (idx < 6) {
item.remove();
}
});
}
this.$allCards.forEach((card, idx) => {
this.observer.observe(card);
});
const lastCardObserver = new IntersectionObserver((entries) => {
const $lastCard = entries[0];
if (!$lastCard.isIntersecting) return;
this.loadNewCards();
lastCardObserver.unobserve($lastCard.target);
});
lastCardObserver.observe(document.querySelector('.card:last-child'));
}
}
const cardGenerator = new CardGenerator();
html,
body {
height: 100%;
width: 100%;
box-sizing: border-box;
padding: 0;
margin: 0;
}
.card {
float: left;
width: 48vw;
margin: 1%;
transform: translateX(100px);
opacity: 0;
transition: 150ms;
}
.card.show {
transform: translateY(0);
opacity: 1;
}
.card img {
width: 100%;
border-radius: 15px;
height: 30vh;
object-fit: cover;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Dog Random Images</h1>
<div class="card-container"></div>
</body>
<script src="app.js" ></script>
</html>
When deleting elements, the entire contents of the container are shifted and observers start to fire. In order for observers not to be triggered when deleting an element, it is necessary to shift the scroll just before deleting the element to the height of this element.
Example below:
class CardGenerator {
constructor() {
this.$cardContainer = document.querySelector('.card-container');
this.$allCards = undefined;
this.observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
entry.target.classList.add('show', entry.isIntersecting);
if (entry.isIntersecting) {
this.observer.unobserve(entry.target);
}
});
},
{
threshold: 1,
rootMargin: '150px',
}
);
this.loadNewCards();
}
cacheDOMElements() {
this.$allCards = document.querySelectorAll('.card');
}
loadNewCards() {
for (let index = 0; index < 6; index++) {
fetch('https://dog.ceo/api/breeds/image/random', { method: 'GET' })
.then((result) => {
return result.json();
})
.then((r) => {
console.log(r);
const card = document.createElement('div');
card.classList.add('card');
const imageElement = document.createElement('img');
imageElement.classList.add('forza-img');
imageElement.setAttribute('src', r.message);
card.appendChild(imageElement);
this.observer.observe(card);
this.$cardContainer.append(card);
this.cacheDOMElements();
if (this.$allCards.length % 6 === 0) this.lastFunc();
});
}
}
lastFunc() {
console.log(this.$allCards);
if (this.$allCards.length > 12) {
this.$allCards.forEach((item, idx) => {
if (idx < 6) {
const scrollTop = this.$cardContainer.scrollTop;
const height = item.offsetHeight;
this.$cardContainer.scrollTo(0, Math.max(0, scrollTop - height));
item.remove();
}
});
}
this.$allCards.forEach((card, idx) => {
this.observer.observe(card);
});
const lastCardObserver = new IntersectionObserver((entries) => {
const $lastCard = entries[0];
if (!$lastCard.isIntersecting) return;
this.loadNewCards();
lastCardObserver.unobserve($lastCard.target);
});
lastCardObserver.observe(document.querySelector('.card:last-child'));
}
}
const cardGenerator = new CardGenerator();
html,
body {
height: 100%;
width: 100%;
box-sizing: border-box;
padding: 0;
margin: 0;
}
.card {
float: left;
width: 48vw;
margin: 1%;
transform: translateX(100px);
opacity: 0;
transition: 150ms;
}
.card.show {
transform: translateY(0);
opacity: 1;
}
.card img {
width: 100%;
border-radius: 15px;
height: 30vh;
object-fit: cover;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Dog Random Images</h1>
<div class="card-container"></div>
</body>
<script src="app.js" ></script>
</html>
I hope this will help you somehow.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<style>
body {
height: 100%;
width: 100%;
box-sizing: border-box;
padding: 0;
margin: 0;
}
.card {
width: 48vw;
margin: 1%;
}
.card.show {
// opacity: 1;
}
.card img {
width: 100%;
border-radius: 15px;
height: 30vh;
object-fit: cover;
}
.card-container{
border: solid 1px #00f;
padding: 20px;
overflow-y:scroll;
}
</style>
<style>
#sentinel{
height:0px;
}
</style>
<h1>Dog Random Images</h1>
<div class="card-container">
<div id="sentinel"></div>
</div>
<script>
/* Question on https://stackoverflow.com/questions/70482606/delete-elements-that-have-intersected-the-viewport */
class CardGenerator {
constructor() {
this.$cardContainer = document.querySelector('.card-container');
this.$allCards = undefined;
this.mysentinel = document.querySelector('#sentinel');
this.observer = new IntersectionObserver(
(entries) => {
let [entry] = entries; //destructure array, get first entry - should only be 1 - sentinel
if (entry.isIntersecting) {
this.observer.unobserve(entry.target);
this.loadNewCards();
}
}, {
threshold: 1,
rootMargin: '150px' /*expanded root/viewport(due to null) by 150px*/,
}
);
this.loadNewCards();
} // end constructor;
cacheDOMElements() {
//The Document method querySelectorAll() returns a static (not live) NodeList
this.$allCards = document.querySelectorAll('.card');
}
loadNewCards() {
/* https://stackoverflow.com/questions/31710768/how-can-i-fetch-an-array-of-urls-with-promise-all , from peirix*/
this.mypromises = [];
this.mymessages = [];
this.urls = new Array(6).fill("https://dog.ceo/api/breeds/image/random", 0, 6);
//create array of promises
var promises = this.urls.map(url => fetch(url).then(y => y.json()));
//Promise.all() method takes an iterable of promises
//promise.all returns a single Promise that resolves to an array of the results of the input promises
Promise.all(promises)
.then(results => {
//accumulate all the urls from message property
results.forEach(v => this.mymessages.push(v.message));
})
.finally(() => {
let idx = 0;
for (let message of this.mymessages) {
const card = document.createElement('div');
card.classList.add('card');
const imageElement = document.createElement('img');
imageElement.setAttribute('src', message);
imageElement.setAttribute('title', `${idx++}:${message}`);
card.appendChild(imageElement);
this.$cardContainer.appendChild(card);
}// end for
this.cacheDOMElements();
//stop this sentinel possibly hitting the observer to loadnewcards as we (re)move cards
this.observer.unobserve(this.mysentinel);
//if number of cards is>12 then takeoff the first 6
if (this.$allCards.length > 12) {
for (let i = 0; i < 6; i++) {
this.$allCards[i].remove();
}
}
//already exists so move it to bottom of container div
this.$cardContainer.appendChild(this.mysentinel);
/*this should be outside the root so when it invokes observer it will not fire loadnewcards*/
this.observer.observe(this.mysentinel);
}); //end of finally end of Promise.all
} //end loadnewcards
} //class CardGenerator
const cardGenerator = new CardGenerator();
</script>
</body>
</html>
Related
I have an API list and I want my divs to float within this huge container. this is my reference - but for some reason this doesn't work for mine (http://jsfiddle.net/bsd3b0r0/1/) I know I'm probably missing something, naming of my divs or something but I simply cannot figure it out. Would appreciate any advice.
/* globals require */
console.log("Hello, Airtable");
let wrapper = document.querySelector(".container");
// load the airtable library, call it "Airtable"
let Airtable = require("airtable");
console.log(Airtable);
// use the airtable library, connect to our base using API key
let base = new Airtable({ apiKey: "keyXfgdNIcv7dW63l" }).base(
"appWiQM0htfVBj9RL"
);
//get the "books" table from the base, select ALL the records, and specify the functions that will receive the data
base("nature_sounds").select({}).eachPage(gotPageOfSounds, gotAllSounds);
// an empty array to hold our book data
let sounds = [];
// callback function that receives our data
function gotPageOfSounds(records, fetchNextPage) {
console.log("gotPageOfSounds()");
// add the records from this page to our books array
sounds.push(...records);
// request more pages
fetchNextPage();
}
// call back function that is called when all pages are loaded
function gotAllSounds(err) {
console.log("gotAllSounds()");
// report an error, you'd want to do something better than this in production
if (err) {
console.log("error loading sounds");
console.error(err);
return;
}
// call functions to log and show the books
consoleLogSounds();
showSounds();
}
// just loop through the books and console.log them
function consoleLogSounds() {
console.log("consoleLogSounds()");
sounds.forEach((sound) => {
console.log("Sound:", sound);
});
}
// loop through the books, create an h2 for each one, and add it to the page
function showSounds() {
console.log("showSounds()");
sounds.forEach((sound) => {
let soundTextHolder = document.createElement("div"); // victoria u changed it from h2 to div dont forget//
soundTextHolder.classList.add("entry");
soundTextHolder.innerText = sound.fields.emotion;
wrapper.appendChild(soundTextHolder);
let soundColor = sound.fields.color_hex_code;
soundTextHolder.style.backgroundColor = soundColor;
let audioHolder = document.createElement("audio");
audioHolder.src = sound.fields.audio_file[0].url;
audioHolder.classList.add("soundClass");
audioHolder.controls = true;
audioHolder.autoplay = true;
audioHolder.loop = true;
soundTextHolder.appendChild(audioHolder);
});
}
// trying to aniamte//
var divsize = 5,
divcount = 50;
var gRows = Math.floor($(".container").innerWidth() / divsize);
var gCols = Math.floor($('.container').innerHeight() / divsize);
var vals = _.shuffle(_.range(divcount));
var xpos = _.shuffle(_.range(gRows));
var ypos = _.shuffle(_.range(gCols));
_.each(vals, function(d, i) {
var $newdiv = $('<div/>').addClass("div");
$newdiv.css({
'position': 'absolute',
'left': (xpos[i] * divsize) + 'px',
'top': (ypos[i] * divsize) + 'px'
}).appendTo('.container').html(d);
animateDiv();
});
function newPosition() {
// Get viewport dimensions (remove the dimension of the div)
var h = $('.container').height() - 50;
var w = $('.container').width() - 50;
var newh = Math.floor(Math.random() * h);
var neww = Math.floor(Math.random() * w);
return [newh, neww];
}
function animateDiv() {
var newp = newPosition();
var oldp = $('div').offset();
var speed = 3000;
$('div').animate({
top: newp[0],
left: newp[1]
}, speed, function() {
animateDiv();
});
};
// // // collect all the divs
// var divs = document.getElementsByTagName('h2');
// // // get window width and height
// / var winWidth = window.innerWidth;
// / var winHeight = window.innerHeight;
// // // i stands for "index". you could also call this banana or haircut. it's a variable
// // for ( var i=0; i < divs.length; i++ ) {
// // // shortcut! the current div in the list
// // var thisDiv = divs[i];
// // // get random numbers for each element
// // randomTop = getRandomNumber(0, winHeight);
// // randomLeft = getRandomNumber(0, winWidth);
// // // update top and left position
// / thisDiv.style.top = randomTop +"px";
// / thisDiv.style.left = randomLeft +"px";
// }
// // function that returns a random number between a min and max
// function getRandomNumber(min, max) {
// return Math.random() * (max - min) + min;
// }
body{
font-family: Space Mono;
background-color: white;
}
h1{
text-align: right;
color: black;
font-family: sans-serif;
}
.container{
width: 100%;
/* align-items: center;
display: grid;
grid-row: inherit;
position: relative; */
}
div{
border-radius: 50% 20% / 10% 40%;
/* border-radius: 10% / 50%; */
width: 30vw;
margin-left: auto;
margin-right: auto;
text-align: center;
/* display: flex; */
flex-direction: column;
box-shadow: inset 0 0 2000px rgba(255, 255, 255, .5);
filter: blur(1px);
/* animation-name: floating;
animation-duration: 3s;
animation-iteration-count: infinite;
animation-timing-function: ease-in-out; */
}
/* #keyframes floating {
0% { transform: translate(0, 0px); }
50% { transform: translate(0, 15px); }
100% { transform: translate(0, -0px); }
} */
.entry1{
width: 60vw;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>EllasticCollection</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Mono&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>sound and color through emotion</h1>
<div id="myData" class="container">
<div class="individual-colors"></div>
</div>
<script src="airtable.browser.js" defer></script>
<script src="script.js" defer></script>
</body>
</html>
I am trying to create an "Etch-A-Sketch"-program, which should let the user draw only by clicking or holding the mouse-button. How can I realize that in JavaScript?
Somehow after the user chooses a color via clicking on the color-button, the color is already drawn as soon as the mouse cursor enters the drawing area (div class="container").
I've tried several functions, but it's still not working as expected...
Could someone please provide a hint?
"use strict";
const divContainer = document.querySelector(".container");
const btnsContainer = document.querySelector(".buttons");
const btnBlack = document.createElement("button");
const btnGreyScale = document.createElement("button");
const btnRgb = document.createElement("button");
const btnErase = document.createElement("button");
const btnShake = document.createElement("button");
function createGrid(col, rows) {
for(let i = 0; i < (col * rows); i++) {
const div = document.createElement("div");
divContainer.style.gridTemplateColumns = `repeat(${col}, 1fr)`;
divContainer.style.gridTemplateRows = `repeat(${rows}, 1fr)`;
divContainer.appendChild(div).classList.add("box");
}
}
createGrid(16,16)
let isDrawing = false;
window.addEventListener("mousedown", () => {
isDrawing = true;
});
window.addEventListener("mouseup", () => {
isDrawing = false;
});
function paintBlack() {
const boxes = divContainer.querySelectorAll(".box");
btnBlack.textContent = "Black";
btnBlack.addEventListener("click", function () {
boxes.forEach(box => box.addEventListener("mouseover", function () {
this.style.background = "#000";
}))
})
btnsContainer.appendChild(btnBlack).classList.add("btn");
}
paintBlack();
function paintGreyScale() {
const boxes = divContainer.querySelectorAll(".box");
btnGreyScale.textContent = "Grey";
btnGreyScale.addEventListener("click", function () {
boxes.forEach(box => box.addEventListener("mouseover", function () {
let randNum = Math.floor(Math.random() * 256);
let grayScale = `rgb(${randNum},${randNum},${randNum})`;
box.style.background = grayScale;
}))
})
btnsContainer.appendChild(btnGreyScale).classList.add("btn");
}
paintGreyScale();
function paintRgb() {
const boxes = divContainer.querySelectorAll(".box");
btnRgb.textContent = "Rainbow";
btnRgb.addEventListener("click", function () {
boxes.forEach(box => box.addEventListener("mouseover", function () {
let r = Math.floor(Math.random() * 256);
let g = Math.floor(Math.random() * 256);
let b = Math.floor(Math.random() * 256);
const rgb = `rgb(${r},${g},${b})`;
box.style.background = rgb;
}))
})
btnsContainer.appendChild(btnRgb).classList.add("btn");
}
paintRgb();
function erase() {
const boxes = divContainer.querySelectorAll(".box");
btnErase.textContent = "Erase";
btnErase.addEventListener("click", function () {
boxes.forEach(box => box.addEventListener("mouseover", function () {
this.style.background = "#FFF";
}))
})
btnsContainer.appendChild(btnErase).classList.add("btn");
}
erase();
function clearCanvas() {
const boxes = divContainer.querySelectorAll(".box");
btnShake.textContent = "Shake it!";
btnShake.addEventListener("click", function () {
boxes.forEach(box => box.style.backgroundColor = "#FFF");
})
btnsContainer.appendChild(btnShake).classList.add("shake");
}
clearCanvas();
btnShake.addEventListener("click", clearCanvas);
*,
*::before,
*::after {
margin: 0;
padding: 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
html {
font-size: 16px;
}
body {
background: linear-gradient(to bottom, #1488CC, #2B32B2);
color: #FFF;
line-height: 1.5;
height: 100vh;
}
#wrapper {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
text-align: center;
}
.container {
width: 500px;
height: 500px;
display: grid;
background-color: #FFF;
box-shadow: 0 0 10px;
}
.box {
border: .5px solid #808080;
}
.shake {
animation: shake .5s linear 1;
}
#keyframes shake {
10%,
90% {
transform: translate3d(-1px, 0, 0);
}
20%,
80% {
transform: translate3d(2px, 0, 0);
}
30%,
50%,
70% {
transform: translate3d(-4px, 0, 0);
}
40%,
60% {
transform: translate3d(4px, 0, 0);
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>Etch-A-Sketch</title>
</head>
<body>
<div id="wrapper">
<main>
<div class="container"></div>
<div class="buttons"></div>
</main>
</div>
<script src="etchAsketch.js"></script>
</body>
</html>
The mousedown event
window.addEventListener("mousedown", () => {
isDrawing = true;
});
sets isDrawing to true, and mouseup event to false, but you never use this variable to check whether the color should be drawn.
Solution: for each statement you have that's setting the background color of a square (except for clearCanvas), wrap it in an if statement checking if the user isDrawing:
if (isDrawing){this.style.background = "#000";} //black
if (isDrawing){this.style.background = grayScale;} //gray
if (isDrawing){this.style.background = rgb;} // rainbow
if (isDrawing){this.style.background = "#FFF";} // erase
boxes.forEach(box => box.style.backgroundColor = "#FFF");
}) //leave clearCanvas as it is
I'm trying to make a string that will write itself letter by letter until completing the sentence, and the speed of appearing each letter is based on an input that varies from 1 to 10. At the end of the string, it will blink for 5 seconds until that an alien will appear. My idea was to create a setInterval to add the letters and when the counter added the array size it would return the final animation of the loop with the new setInterval call, and before it was called again it had already been cleared, and called again in a recursion by setTimout callback to maintain the infinite loop. But it's not reaching setTimout, why?
//script.js
const speedInput = document.getElementsByClassName('speed--input')[0];
const alien = document.getElementsByClassName('alien')[0];
const textDiv = document.getElementsByClassName('text')[0];
const textShow = document.getElementsByClassName('text--show')[0];
const textDB = 'We go to dominate the world.';
const textStr = '';
let count = 0;
let speed = speedInput.value * 100;
const textChangeHandle = (count, textStr, textDB) => setInterval(() => {
if (count < textDB.length) {
textStr += textDB[count];
textShow.innerHTML = textStr;
textDiv.style.width = `${40 * count}px`;
count++;
} else {
textShow.style.animation = 'bip 1s linear 1s infinite'
return () => {
setTimeout(() => {
textShow.style.animation = '';
textStr = '';
count = 0;
textShow.style.opacity = 0;
alien.style.opacity = 1;
setTimeout(() => {
alien.style.opacity = 0;
textShow.style.opacity = 1;
textChangeHandle(count, textStr, textDB)
}, 5000)
}, 5000);
clearInterval(textChangeHandle);
}
}
}, speed)
textChangeHandle(count, textStr, textDB);
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style/style.css">
<title>Document</title>
</head>
<body>
<div class="text">
<p class="text--show"></p>
<img class="alien" src="alien.png" alt="Aki é Jupiter karai">
</div>
<div class="speed">
<span>Speed</span>
<input class="speed--input" type="number" min="1" max="10" value="1">
</div>
<script src="script.js"></script>
</body>
</html>
//style.css
*,
*::after,
*::before {
margin: 0;
padding: 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
body {
font-family: sans-serif;
background-color: #3cd070;
}
.text {
position: fixed;
top: 50%;
left: 50%;
height: auto;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
font-size: 40px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.text .alien {
opacity: 0;
height: 600px;
width: auto;
margin-bottom: 50px;
position: absolute;
}
.text .text--show {
font-size: 40px;
width: 100%;
position: absolute;
display: block;
text-align: center;
-webkit-animation: bip 1s linear 1s infinite;
animation: bip 1s linear 1s infinite;
}
.speed {
position: fixed;
top: 90%;
left: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.2);
display: -webkit-box;
display: -ms-flexbox;
display: flex;
padding: 10px 20px;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
}
.speed .speed--input {
border: 0;
outline: 0;
width: 40px;
height: 25px;
margin: 10px;
text-align: center;
}
#-webkit-keyframes bip {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
#keyframes bip {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
There is also the animation frame api
It will run your function exactly before the next "natural" repaint of the browser.
Additionally and probably useful in your case, it will pass the current time in your callback, so you can take a time detail and animate according to this. That way you can get a smooth animation of equal length for everyone, even when the frame rate is inconsistent.
Consider the following snippet, which will basically cause an infinite loop.
function draw(now) {
requestAnimationFrame(draw)
}
requestAnimationFrame(draw)
Now you only need to remember the time and take the time delta next time the function is called. If enough time has passed, you can write another character. (Normally you would change a value divided by the time delta, but since you have characters, they will be either there or not.
let speed = 300
let timePassedSinceLastChar = 0
let then = null
function draw(now){
now *= 0.001;
const deltaTime = now - then;
then = now;
timePassedSinceLastChar += deltaTime;
if (timePassedSinceLastChar >= speed) {
drawChar()
timePassedSinceLastChar = 0
}
requestAnimationFrame(draw)
}
requestAnimationFrame(draw)
Furthermore, the requestAnimationFrame function returns the id of the requested frame. That allows to cancel the loop.
const frameId = requestAnimationFrame(draw)
cancelAnimationFrame(frameId)
So the final code could look something like this.
const textDB = 'We go to dominate the world.';
let charIndex = 0;
let frameId = null
let then = 0
let sinceDraw = 0
let speed = () => Math.random() * 0.4 + 0.05 // randomize the speed a bit
let $p = document.querySelector('p')
function draw(now) {
// cancel on end of string
if (charIndex >= textDB.length) {
cancelAnimationFrame(frameId)
console.log('done')
return
}
// get the time delta
now *= 0.001; // convert to seconds
const deltaTime = now - then;
then = now;
sinceDraw += deltaTime
// if its time to draw again, do so
if (sinceDraw >= speed()) {
let char = textDB[charIndex]
$p.textContent += char
charIndex++
sinceDraw = 0
}
// request another frame
frameId = requestAnimationFrame(draw)
}
// request the first frame
frameId = requestAnimationFrame(draw)
<p></p>
The issue is that in the else statement, you are returning a function that is never called.
return () => {
setTimeout(() => {
textShow.style.animation = '';
textStr = '';
count = 0;
textShow.style.opacity = 0;
alien.style.opacity = 1;
setTimeout(() => {
alien.style.opacity = 0;
textShow.style.opacity = 1;
textChangeHandle(count, textStr, textDB)
}, 5000)
}, 5000);
clearInterval(textChangeHandle);
}
Just remove the return statment and call the setTimeout function directly.
const textChangeHandle = (count, textStr, textDB) => setInterval(() => {
if (count < textDB.length) {
textStr += textDB[count];
textShow.innerHTML = textStr;
textDiv.style.width = `${40 * count}px`;
count++;
} else {
textShow.style.animation = 'bip 1s linear 1s infinite'
setTimeout(() => {
textShow.style.animation = '';
textStr = '';
count = 0;
textShow.style.opacity = 0;
alien.style.opacity = 1;
setTimeout(() => {
alien.style.opacity = 0;
textShow.style.opacity = 1;
textChangeHandle(count, textStr, textDB)
}, 5000)
}, 5000);
clearInterval(textChangeHandle);
}
}, speed)
I found the solution. With setInterval, the code was creating multiple instances of callbacks and overloading memory.
///script.js
const textEl = document.getElementById('text');
const inputEl = document.getElementById('input');
const alienEl = document.getElementById('alien');
const text = 'We go to dominate the world!';
let idx = 0;
let speed = 300 / inputEl.value;
writeText();
function writeText () {
if(idx === 0) setTimeout(() => textEl.style.opacity = 1, speed)
textEl.innerText = text.slice(0, idx);
idx++;
if (idx > text.length) {
idx = 0;
textEl.style.animation = 'bip 1s linear 1s infinite';
setTimeout(() => {
textEl.style.animation = ''
textEl.style.opacity = 0;
setTimeout(() => {
alienEl.style.opacity = 1;
setTimeout(() => {
alienEl.style.opacity = 0;
setTimeout(writeText, speed);
}, 5000)
}, 1000);
}, 5000)
} else {
setTimeout(writeText, speed);
}
}
inputEl.addEventListener('input', (e) => speed = 300 / e.target.value);
The dragging works but the bug is that when I drag the element somehow "jumps" and does not flow with the mouse. See it in the code. Don't worry about removing event listeners, I will add them as soon as this works.
I have an issue on a draggable "div" element. I've searched many answers before I posted this question but nothing seems to be the solution(or maybe I am not understanding the problem really well).
Thank you!
const hotspot = document.getElementsByClassName("hotspot")[0];
const container = document.getElementsByClassName("container")[0];
let containerRect = container.getBoundingClientRect();
let hsRect = hotspot.getBoundingClientRect();
let relMouse = { x: 0, y: 0 };
let windowMouse = { x: 0, y: 0 };
let isUserIntercating = false;
const handlePointerUp = (e) => {
isUserIntercating = false;
};
const handlePointerDown = (e) => {
isUserIntercating = true;
hsRect = hotspot.getBoundingClientRect();
relMouse = { x: e.pageX - hsRect.x, y: e.pageY - hsRect.y };
window.addEventListener("pointerup", handlePointerUp, false);
};
const handlePointerMove = (e) => {
hsRect = hotspot.getBoundingClientRect();
containerRect = container.getBoundingClientRect();
windowMouse = { x: e.clientX - relMouse.x, y: e.clientY - relMouse.y };
};
const update = (t) => {
requestAnimationFrame(update);
if (isUserIntercating) {
hotspot.style.transform = `translate(${
windowMouse.x - containerRect.x
}px,0px)`;
}
};
update();
hotspot.addEventListener("pointerdown", handlePointerDown, false);
window.addEventListener("pointermove", handlePointerMove, false);
body {
font-family: sans-serif;
margin: 0;
}
.container {
padding: 0;
margin: 50px;
max-width: 600px;
min-height: 600px;
background-color: blanchedalmond;
}
.hotspot {
/* position: absolute; */
background-color: aqua;
/* transform: translate(100px, 100px); */
min-height: 100px;
max-width: 100px;
z-index: 200;
}
<!DOCTYPE html>
<html>
<head>
<title>Parcel Sandbox</title>
<meta charset="UTF-8" />
</head>
<body>
<div class="container">
<div class="hotspot"></div>
</div>
<script src="src/index.js"></script>
</body>
</html>
Try like this:
const hotspot = document.getElementsByClassName("hotspot")[0];
const container = document.getElementsByClassName("container")[0];
let containerRect = container.getBoundingClientRect();
let hsRect = hotspot.getBoundingClientRect();
let relMouse = { x: 0, y: 0 };
let windowMouse = { x: 0, y: 0 };
let isUserIntercating = false;
const handlePointerUp = (e) => {
isUserIntercating = false;
};
const handlePointerDown = (e) => {
isUserIntercating = true;
hsRect = hotspot.getBoundingClientRect();
relMouse = { x: e.pageX - hsRect.x, y: e.pageY - hsRect.y };
window.addEventListener("pointerup", handlePointerUp, false);
};
const handlePointerMove = (e) => {
hsRect = hotspot.getBoundingClientRect();
containerRect = container.getBoundingClientRect();
windowMouse = { x: e.clientX - relMouse.x, y: e.clientY - relMouse.y };
requestAnimationFrame(update);
};
const update = (t) => {
if (isUserIntercating) {
hotspot.style.transform = `translate(${
windowMouse.x - containerRect.x
}px,0px)`;
}
};
hotspot.addEventListener("pointerdown", handlePointerDown, false);
window.addEventListener("pointermove", handlePointerMove, false);
body {
font-family: sans-serif;
margin: 0;
}
.container {
padding: 0;
margin: 50px;
max-width: 600px;
min-height: 600px;
background-color: blanchedalmond;
}
.hotspot {
/* position: absolute; */
background-color: aqua;
/* transform: translate(100px, 100px); */
min-height: 100px;
max-width: 100px;
z-index: 200;
}
<!DOCTYPE html>
<html>
<head>
<title>Parcel Sandbox</title>
<meta charset="UTF-8" />
</head>
<body>
<div class="container">
<div class="hotspot"></div>
</div>
<script src="src/index.js"></script>
</body>
</html>
The issue seemed to be that the update function kept calling itself all the time, which is not really ideal. The update should only be called in the handlePointerMove function (only change the hotspot position when the mouse moves).
I wanted to make an effect similar to "click and hold" of this page, but with some changes, with svg forms, the point is that I did two functions that do what I wanted to do very well, but at the moment I introduced another svg form, the Data of the effect is transferred to the other, affecting the execution of the functions, the question is, how do I prevent this from happening?
Note: The best way to see what is happening is to let one of the two complete.
Here is an example of what I have programmed
of course I leave you all the code that I have been working
//Efect Drivers
class EffectValues {
constructor(count, time, initOffset, label) {
this.count = count;
this.time = time;
this.initOffset = initOffset;
this.label = label;
}
}
//Controlers
let counter; //it will be interval controller
let globalCount = 0;
//Call objects DOM
const loader = document.querySelector('.loader');
const circle = document.querySelector('.circle');
const svgText = document.querySelector('.svgText');
const textSvg = document.querySelector('.textSvg');
//Preloader svg
const startCircle = new EffectValues(0, 3, 1300, circle);
const showEffect = new EffectValues(0, 3, 500, svgText);
//Mouse events
// Circle
loader.addEventListener('mousedown', e => {
increase(e, startCircle);
});
loader.addEventListener('mouseup', e => {
decrease(e, startCircle);
});
// Text SVG
textSvg.addEventListener('mousedown', e => {
increase(e, showEffect);
});
textSvg.addEventListener('mouseup', e => {
decrease(e, showEffect);
});
//main functions
const increase = (e, { count, initOffset, time, label }) => {
let flag = true;
// console.log(flag);
clearInterval(counter);
while (e.type == 'mousedown') {
counter = setInterval(() => {
if (globalCount < initOffset) {
count = initOffset - globalCount;
label.style.strokeDashoffset = count;
globalCount++;
}
}, time);
break;
}
return flag;
};
const decrease = (e, { count, initOffset, time, label }) => {
let flag = true;
// console.log(flag);
clearInterval(counter);
while (e.type == 'mouseup') {
counter = setInterval(() => {
if (globalCount >= 0 && globalCount < initOffset) {
count = -globalCount + initOffset;
label.style.strokeDashoffset = count;
globalCount--;
} else {
flag = false;
}
}, time);
break;
}
return flag;
};
:root {
--dark: #2f3640;
--dark-light: #353b48;
--blue: #192a56;
--blue-dark: #273c75;
--cian: #0097e6;
--cian-light: #00a8ff;
--orange: #c23616;
--orange-light: #e84118;
}
* {
margin: 0;
padding: 0;
}
body {
min-height: 100vh;
background-color: var(--dark);
display: flex;
justify-content: center;
align-content: center;
}
.loader {
position: relative;
width: 50%;
height: 100vh;
}
.loader svg {
position: absolute;
width: 550px;
height: 550px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.loader svg circle {
width: 100%;
height: 100%;
fill: none;
stroke-width: 10;
stroke: var(--cian);
stroke-linecap: round;
transform: translate(5px, 5px);
stroke-dasharray: 1300;
stroke-dashoffset: 1300;
}
.textSvg {
position: relative;
width: 40%;
}
.textSvg svg text {
stroke: var(--orange-light);
fill: none;
stroke-width: 3;
stroke-dasharray: 500;
stroke-dashoffset: 500;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="styles.css" />
<title>Loader</title>
</head>
<body>
<div class="loader">
<svg>
<circle class="circle" cx="200" cy="200" r="200"></circle>
</svg>
</div>
<div class="textSvg">
<svg
xmlns="http://www.w3.org/2000/svg"
width="1413"
height="274"
viewBox="0 0 1413 274"
>
<text
class="svgText"
transform="translate(0 198)"
fill="#c6e0ee"
font-size="100"
font-family="MonotypeCorsiva, Monotype Corsiva"
>
<tspan x="0" y="0">David Figueroa</tspan>
</text>
</svg>
</div>
</body>
<script src="main.js" defer></script>
</html>
I have been looking for information but nothing has helped me.
Beforehand thank you very much
You can implement your requirements by moving global variables and functions inside the class.
Codepen here
//Efect Drivers
class EffectValues {
constructor(time, initOffset, label) {
this.time = time;
this.initOffset = initOffset;
this.label = label;
this.counter;
this.globalCount = 0;
}
increase(e) {
let flag = true;
// console.log(flag);
clearInterval(this.counter);
while (e.type == 'mousedown') {
this.counter = setInterval(() => {
if (this.globalCount < this.initOffset) {
const count = this.initOffset - this.globalCount;
this.label.style.strokeDashoffset = count;
this.globalCount++;
}
}, this.time);
break;
}
return flag;
};
decrease(e) {
let flag = true;
// console.log(flag);
clearInterval(this.counter);
while (e.type == 'mouseup') {
this.counter = setInterval(() => {
if (this.globalCount >= 0 && this.globalCount < this.initOffset) {
const count = -this.globalCount + this.initOffset;
this.label.style.strokeDashoffset = count;
this.globalCount--;
} else {
flag = false;
}
}, this.time);
break;
}
return flag;
};
}
//Call objects DOM
const loader = document.querySelector('.loader');
const circle = document.querySelector('.circle');
const svgText = document.querySelector('.svgText');
const textSvg = document.querySelector('.textSvg');
//Preloader svg
const startCircle = new EffectValues(3, 1300, circle);
const letterEffect = new EffectValues(3, 500, svgText);
//Mouse events
// Circle
loader.addEventListener('mousedown', e => {
startCircle.increase(e);
});
loader.addEventListener('mouseup', e => {
startCircle.decrease(e);
});
// Text SVG
textSvg.addEventListener('mousedown', e => {
letterEffect.increase(e);
});
textSvg.addEventListener('mouseup', e => {
letterEffect.decrease(e);
});