ReadyStateChange after all DOM Elements are create, else many SVG animation? - javascript

I have many SVG animation bodymovin.load, so i need to create preloader with persent, where persent every img in all animation
All work is ok, BUT
document.addEventListener('DOMContentLoaded', () => {
starts before all image svg create at DOM, for example in first animation load svg 60 images, in second 367, and preloader see only 60 images
It's preloader
document.addEventListener('DOMContentLoaded', () => {
const mediaFiles = document.querySelectorAll('image');
console.log(mediaFiles)
let i = 0
Array.from(mediaFiles).forEach((file, index) => {
file.onload = () => {
i++
percents.innerHTML = ((i * 100) / mediaFiles.length).toFixed(1)
if(i === mediaFiles.length) {
preloader.classList.add('preloader-hide')
percents.innerHTML = 100
}
}
})
})
It's animations
const ramaoknaData = {
container: document.getElementById('ramaokna-container'),
renderer: 'svg',
loop: false,
autoplay: false,
path: '/ramaokna.json'
};
const ramaoknaAnim = bodymovin.loadAnimation(ramaoknaData);

Related

setTimeout in object method continues to run after object is destroyed

I have a class called Bullet which is essentially a div on a space invader webpage. When this bullet gets 'fired' I call a method which gradually moves the 'bullet' up the screen.
When the bullet gets to the edge of the screen I want to remove the whole bullet object from memory. However, the setTimeout loop continues to run even after I've deleted it (I think).
I'm sure there is a better way to do this! Perhaps it's foolish to run the loop like this?
TIA
this.bulletmove = new CustomEvent("bulletmove",{detail:this.name});
...
/**
* moves the bullet up the screen gradually
*/
fire(){
var that = this;
setTimeout(function(){
that.moveUp();
window.dispatchEvent(that.bulletmove);
that.fire();
},50);
}
The event is picked up in a controller script which checks if the bullet has reached the edge of the screen at which point it is deleted:
window.addEventListener('bulletmove', function(evt) {
checkCollision(evt);
},false);
...
/**
*Check if the bullet has gone off screen and deletes it
**/
function checkCollision(e){
var bulletName = e.detail;
var bullet = bullets[bulletName];
//check if the bullet has gone off screen
if (bullet.bottom < 0){
bullet.destroy;
delete bullets[e.detail];
bullet=null;
}
}
Have you tried a clearTimeout method to stop the setTimeout from firing?
https://www.freecodecamp.org/news/javascript-settimeout-how-to-set-a-timer-in-javascript-or-sleep-for-n-seconds/
const fireBullet = setTimeout(function(){
that.moveUp();
window.dispatchEvent(that.bulletmove);
that.fire();
},50);
clearTimeout(fireBullet)
I think you should use setInterval instead of calling fire() again - by calling that function, a new setTimeout is created (with a new handler); before the removal of the object, you call obj.halt(), and that clears the setInterval correctly.
const obj = {
name: "objName",
bulletmove() {
return new CustomEvent("bulletmove", {
detail: this.name
})
},
halt() {
clearInterval(this.intervalHandler)
},
intervalHandler: null,
fire() {
const handler = setInterval(() => {
// this.moveUp()
// console.log("move up")
window.dispatchEvent(this.bulletmove())
// this.fire()
}, 500)
this.intervalHandler = handler
},
}
let i = 0
window.addEventListener('bulletmove', function(e) {
// this if-else if mocks the collision detection
// expected: log obj.name 5 times, then clear the interval,
// then event should not be called anymore
if (i < 5) {
console.log(i, e.detail)
} else if (i < 8) {
obj.halt()
console.log(i)
} else if (i < 100) {
console.log(i, e.detail)
}
i++
})
obj.fire()
ANOTHER WAY
A cleaner approach would be if the fire method returned its own "clear function", and you could use that in the event handling:
const obj = {
name: "objName",
bulletmove() {
return new CustomEvent("bulletmove", {
detail: this.name
})
},
fire() {
const handler = setInterval(() => {
// this.moveUp()
// console.log("move up")
window.dispatchEvent(this.bulletmove())
// this.fire()
}, 500)
return () => clearInterval(handler)
},
}
let i = 0
const fireHandler = obj.fire()
const eventHandler = (clearFn) => (e) => {
// this if-else if mocks the collision detection
// expected: log obj.name 5 times, then clear the interval,
// then event should not be called anymore
if (i < 5) {
console.log(i, e.detail)
} else if (i < 8) {
clearFn()
console.log(i)
} else if (i < 100) {
console.log(i, e.detail)
}
i++
}
const eventHandlerWithRemoveFn = eventHandler(fireHandler)
window.addEventListener('bulletmove', eventHandlerWithRemoveFn)
The drawback of this method is that you need to add each object's event handler separately to the window, its benefit is more control, cleaner code (no need to save that handler in the object).
A MODIFIED VERSION FOR MULTIPLE INTERVALS
This is a version of the previous solution, where the clearing functions are stored in the window object:
const eventHandler = (e) => {
const i = e.detail.eventCounter
if (i < 3) {
console.log(i, e.detail.name)
} else if (i < 4) {
window.bulletIntervals[e.detail.name]()
console.log(i, e.detail.name + " is halted")
} else if (i < 100) {
console.log(i, e.detail.name)
}
}
const getBullet = (i) => ({
eventCounter: i, // only for mocking!
name: `objName-${i}`,
bulletmove() {
return new CustomEvent("bulletmove", {
detail: {
name: this.name,
eventCounter: this.eventCounter,
}
})
},
fire() {
const handler = setInterval(() => {
window.dispatchEvent(this.bulletmove())
this.eventCounter++
}, 500)
if (!window.bulletIntervals) window.bulletIntervals = {}
window.bulletIntervals[this.name] = () => clearInterval(handler)
},
})
const bullets = [
getBullet(0),
getBullet(1),
getBullet(2),
]
const fireAll = (bullets) => {
window.addEventListener("bulletmove", eventHandler)
bullets.forEach((bullet) => {
bullet.fire()
})
}
fireAll(bullets)
I would use RxJS to monitor the progress of your bullets.
In the example below I have three different bullets. Each within its own boundary. Once fired, they will immediately stop when they exit their box.
For each bullet we have an "animation frame" observable that emits when such a frame is made available by the browser (internally RxJS uses requestAnimationFrame for this). At that point we check whether the bullet is still within its parent bounding box. If it is we move it otherwise we don't and the subscription to the animation frame stream automatically ends.
const rightPos = el => el.getBoundingClientRect().right;
const moveBullet = (sel, pos) =>
document.querySelector(sel)
.style.left = `${pos}px`;
const fire = (bullet) => {
const el = document.querySelector(bullet);
const parentPos = rightPos(el.parentNode);
return animationFrames().pipe(
map(() => rightPos(el)),
takeWhile(pos => pos < parentPos)
);
}
const bullet1$ = fire('#bullet1');
const bullet2$ = fire('#bullet2');
const bullet3$ = fire('#bullet3');
const fire$ = fromEvent(document.querySelector('button'),'click');
fire$.subscribe(() => {
bullet1$.subscribe(pos => moveBullet('#bullet1', pos+1));
bullet2$.subscribe(pos => moveBullet('#bullet2', pos+1));
bullet3$.subscribe(pos => moveBullet('#bullet3', pos+1));
});
div {
height: 30px;
border: 1px solid black;
margin: 5px;
position: relative;
}
span { position: absolute; }
<script src="https://unpkg.com/rxjs#7.5.7/dist/bundles/rxjs.umd.min.js"></script>
<script>
const {animationFrames, fromEvent} = rxjs;
const {map, takeWhile} = rxjs.operators;
</script>
<div style="width:150px"><span id="bullet1">🏉</span></div>
<div style="width:300px"><span id="bullet2">🥏</span></div>
<div style="width:450px"><span id="bullet3">⚽️</span></div>
<button>Fire!</button>
In the following code, an outer div forms the boundaries of a playfield and all game elements inside the playfield are represented by divs.
Game state consists of an array of game elements (in this instance: one ship and zero or more bullets). Once an element is no longer visible (here: simply off the right-hand side of the playfield), it is removed from game state.
The game loop uses requestAnimationFrame to repeatedly render the game state to the playfield.
Bullet position is calculated using the time of firing and the time elapsed (I added a little randomness to bullet velocity just for fun).
Game elements such as bullets have an associated generator function called as part of the game loop, to retrieve the next state of the element (a bullet "moves by itself" after the initial appearance).
Firing a bullet in this design is as simple as creating a new bullet object with an initial position and an instance of a generator function to account for its trajectory; and then adding that pair to the game state.
const elem = ({ kind = 'div', classN = '' }) => {
const el = document.createElement(kind)
el.classList.add(classN)
return el
}
const applyStyle = (el, style) =>
(Object.entries(style)
.forEach(([k, v]) => el.style[k] = v), el)
const cssPixels = (str) => +(str.slice(0, -2))
const isVisible = (left) =>
cssPixels(left) < cssPixels(playfield.style.width)
const createPlayfield = () =>
applyStyle(elem({ classN: 'playfield' }), { width: '300px' })
const createShip = (startLeft, width) =>
[{ classN: 'ship', style: { left: startLeft, width } }, null]
const createBullet = (startLeft) => {
const b = {
classN: 'bullet',
style: { left: startLeft },
firingTime: +new Date(),
velocity: 0.5,
velocitySeed: Number('1.' + ~~(Math.random() * 9)),
startLeft
}
const g = bulletStateGen(b)
return [ b, () => g.next() ]
}
const bulletPos = ({ firingTime,
startLeft,
velocity,
velocitySeed }, now = +new Date()) =>
`${~~(velocity * (now - firingTime) * velocitySeed + cssPixels(startLeft))}px`
const bulletStateGen = function*(b) {
while (1) {
const left = bulletPos(b)
if (!isVisible(left))
break
b.style = { left }
yield(b)
}
}
const fire = (startLeft) =>
state.unshift(createBullet(startLeft))
const tick = () =>
state = state.reduce((acc, [o, next]) => {
if (!next)
return acc.push([o, next]), acc
const { value, done } = next()
if (done)
return acc
return acc.push([value, next]), acc
}, [])
const blank = () => playfield.innerHTML = ''
const render = () => {
blank()
state.forEach(([{ classN, style = {} }]) =>
playfield.appendChild(applyStyle(elem({ classN }), style)))
}
let ship = createShip('10px', '50px')
let state = [ship]
let playfield = createPlayfield()
const gameLoop = () =>
(render(), tick(), requestAnimationFrame(gameLoop))
const init = () => {
document.body.appendChild(playfield)
document.body.onkeyup = (e) =>
e.key === " "
&& fire(`${cssPixels(ship[0].style.left) + cssPixels(ship[0].style.width)}px`)
}
init()
gameLoop(state, playfield)
.playfield {
height: 300px;
background-color: black;
position: relative;
}
.ship {
top: 138px;
height: 50px;
background-color: gold;
position: absolute;
border-radius: 7px 22px 22px 7px;
}
.bullet {
top: 163px;
width: 10px;
height: 2px;
background-color: silver;
position: absolute;
}
Click on the game to focus it, and then press spacebar to fire!

Run a function when animation completes using Curtains.js

I'm looking to call a function right at the end of the wobble effect.
That is, at the end of the damping effect (when the wobble stops), I'd like to execute a GSAP timeline function. I'd assume this type of "onComplete" function would need to be called inside the onReady() of Curtains and perhaps by tracking the damping effect. I'm only familiar with GSAP's onComplete function, but don't know how I would implement it here. Maybe something that checks if deltas.applied is less than 0.001, then the function is called?
Below is the code snippet (without the fragment and vertex shaders). Full working code here:
CodePen
class Img {
constructor() {
const curtain = new Curtains({
container: "canvas",
watchScroll: false,
});
const params = {
vertexShader,
fragmentShader,
uniforms: {
time: {
name: "uTime",
type: "1f",
value: 0,
},
prog: {
name: "uProg",
type: "1f",
value: 0,
}
}
}
const planeElements = document.getElementsByClassName("plane")[0];
this.plane = curtain.addPlane(planeElements, params);
if (this.plane) {
this.plane
.onReady(() => {
this.introAnim();
})
.onRender(() => {
this.plane.uniforms.time.value++;
deltas.applied += (deltas.max - deltas.applied) * 0.05;
deltas.max += (0 - deltas.max) * 0.07;
this.plane.uniforms.prog.value = deltas.applied
})
}
// error handling
curtain.onError(function() {
document.body.classList.add("no-curtains");
});
}
introAnim() {
deltas.max = 6;
//console.log("complete") <-- need an onComplete type function~!
}
}
window.onload = function() {
const img = new Img();
}
What you could use is some algebra :)
First off, you should simplify your deltas.max function like so:
deltas.max += (0 - deltas.max) * 0.07;
// Simplifies to
deltas.max -= deltas.max * 0.07;
// Rewrite to
deltas.max = deltas.max - deltas.max * 0.07;
// Rewrite to
deltas.max = deltas.max * (1 - 0.07);
// Simplifies to
deltas.max *= 0.93; // Much nicer :)
That is actually pretty important to do because it makes our work of calculating the end value of our time variable and the duration of our animation significantly Easier:
// Given deltas.max *= 0.93, need to calculate end time value
// endVal = startVal * reductionFactor^n
// Rewrite as
// n = ln(endVal / startVal) / ln(reductionFactor) // for more see https://www.purplemath.com/modules/solvexpo2.htm
// n = ln(0.001 / 8) / ln(0.93)
const n = 123.84;
// Assuming 60fps normally: n / 60
const dur = 2.064;
Once we have those values all we have to do is create a linear tween animating our time to that value with that duration and update the max and prog values in the onUpdate:
gsap.to(this.plane.uniforms.time, {
value: n,
duration: dur,
ease: "none",
onUpdate: () => {
this.deltas.applied += (this.deltas.max - this.deltas.applied) * 0.05;
this.deltas.max *= 0.93;
this.plane.uniforms.prog.value = this.deltas.applied;
},
onComplete: () => console.log("complete!")
});
Then you get "complete!" when the animation finishes!
To make sure that your Curtains animations run at the proper rate even with monitors with high refresh rates (even the ones not directly animated with GSAP) it's also a good idea to turn off Curtain's autoRendering and use GSAP's ticker instead:
const curtains = new Curtains({ container: "canvas", autoRender: false });
// Use a single rAF for both GSAP and Curtains
function renderScene() {
curtains.render();
}
gsap.ticker.add(renderScene);
Altogether you get this demo.
This won't be the best answer possible but you can take some ideas and insights from it.
Open the console and see that when the animation gets completed it gets fired only once.
//Fire an onComplete event and listen for that
const event = new Event('onComplete');
class Img {
constructor() {
// Added a instance variable for encapsulation
this.animComplete = {anim1: false}
//Changed code above
const curtain = new Curtains({
container: "canvas",
watchScroll: false,
});
const params = {
vertexShader,
fragmentShader,
uniforms: {
time: {
name: "uTime",
type: "1f",
value: 0,
},
prog: {
name: "uProg",
type: "1f",
value: 0,
}
}
}
const planeElements = document.getElementsByClassName("plane")[0];
this.plane = curtain.addPlane(planeElements, params);
if (this.plane) {
this.plane
.onReady(() => {
this.introAnim();
document.addEventListener('onComplete', ()=>{
//Do damping effects here
console.log('complete')
})
})
.onRender(() => {
this.plane.uniforms.time.value++;
deltas.applied += (deltas.max - deltas.applied) * 0.05;
deltas.max += (0 - deltas.max) * 0.07;
this.plane.uniforms.prog.value = deltas.applied
if(deltas.applied<0.001 && !this.animComplete.anim1){
document.dispatchEvent(event)
this.animComplete.anim1 = true
}
})
}
// error handling
curtain.onError(function() {
document.body.classList.add("no-curtains");
});
}
introAnim() {
deltas.max = 6;
}
}
window.onload = function() {
const img = new Img();
}
I've found a solution to call a function at the end of the damping (wobble) effect, that doesn't use GSAP, but uses the Curtains onRender method. Because the uTime value goes up infinitely and the uProg value approaches 0, By tracking both the uTime and uProg values inside the Curtains onRender method we can find a point (2 thresholds) at which the damping effect has essentially completed. Not sure if this is the most efficient way, but it seems to work.
.onRender(() => {
if (this.plane.uniforms.prog.value < 0.008 && this.plane.uniforms.time.value > 50) { console.log("complete")}
})
Thanks to the Curtains docs re asynchronous-textures, I was able to better control the timing of the wobble effect with the desired result every time. That is, on computers with lower FPS, the entire damping effect takes place smoothly, with an onComplete function called at the end, as well as on comps with higher frame rates.
Although, as mentioned there is less control over the length of the effect, as we are not using GSAP to control the Utime values. Thanks #Zach! However, using a "threshold check" inside the curtains onRender this way, means the damping wobble effect is never compromised, if we were to disable the drawing at the on complete call.
By enabling the drawing at the same time the image is loaded we avoid any erratic behaviour. The following works now with hard refresh as well.
export default class Img {
constructor() {
this.deltas = {
max: 0,
applied: 0,
};
this.curtain = new Curtains({
container: "canvas",
watchScroll: false,
pixelRatio: Math.min(1.5, window.devicePixelRatio),
});
this.params = {
vertexShader,
fragmentShader,
uniforms: {
time: {
name: "uTime",
type: "1f",
value: 0,
},
prog: {
name: "uProg",
type: "1f",
value: 0,
},
},
};
this.planeElements = document.getElementsByClassName("plane")[0];
this.curtain.onError(() => document.body.classList.add("no-curtains"));
this.curtain.disableDrawing(); // disable drawing to begin with to prevent erratic timing issues
this.init();
}
init() {
this.plane = new Plane(this.curtain, this.planeElements, this.params);
this.playWobble();
}
loaded() {
return new Promise((resolve) => {
// load image and enable drawing as soon as it's ready
const asyncImgElements = document
.getElementById("async-textures-wrapper")
.getElementsByTagName("img");
// track image loading
let imagesLoaded = 0;
const imagesToLoad = asyncImgElements.length;
// load the images
this.plane.loadImages(asyncImgElements, {
// textures options
// improve texture rendering on small screens with LINEAR_MIPMAP_NEAREST minFilter
minFilter: this.curtain.gl.LINEAR_MIPMAP_NEAREST,
});
this.plane.onLoading(() => {
imagesLoaded++;
if (imagesLoaded === imagesToLoad) {
console.log("loaded");
// everything is ready, we need to render at least one frame
this.curtain.needRender();
// if window has been resized between plane creation and image loading, we need to trigger a resize
this.plane.resize();
// show our plane now
this.plane.visible = true;
this.curtain.enableDrawing();
resolve();
}
});
});
}
playWobble() {
if (this.plane) {
this.plane
.onReady(() => {
this.deltas.max = 7; // 7
})
.onRender(() => {
this.plane.uniforms.time.value++;
this.deltas.applied += (this.deltas.max - this.deltas.applied) * 0.05;
this.deltas.max += (0 - this.deltas.max) * 0.07;
this.plane.uniforms.prog.value = this.deltas.applied;
console.log(this.plane.uniforms.prog.value);
// ---- "on complete" working!! ( even on hard refresh) -----//
if (
this.plane.uniforms.prog.value < 0.001 &&
this.plane.uniforms.time.value > 50
) {
console.log("complete");
this.curtain.disableDrawing();
}
});
}
}
destroy() {
if (this.plane) {
this.curtain.disableDrawing();
this.curtain.dispose();
this.plane.remove();
}
}
}
const img = new Img();
Promise.all([img.loaded()]).then(() => {
console.log("animation started");
});

Where to put setInterval and clearInterval

I have a working slider. I would like to to autoplay the slides and stop the autoplay when user clicks the next or previous button.
I have tried to include setInterval-function but I am not sure how I should apply it in order to make it work. So far I did not manage to do this.
enter code here
var interleaveOffset = 0.2;
var swiperOptions = {
loop: true,
speed: 500,
grabCursor: true,
watchSlidesProgress: true,
mousewheelControl: true,
keyboardControl: true,
navigation: {
nextEl: ".swiper-button-next",
prevEl: ".swiper-button-prev"
},
on: {
progress: function() {
var swiper = this;
for (var i = 0; i < swiper.slides.length; i++) {
var slideProgress = swiper.slides[i].progress;
var innerOffset = swiper.width * interleaveOffset;
var innerTranslate = slideProgress * innerOffset;
swiper.slides[i].querySelector(".slide-inner").style.transform =
"translate3d(" + innerTranslate + "px, 0, 0)";
}
},
touchStart: function() {
var swiper = this;
for (var i = 0; i < swiper.slides.length; i++) {
swiper.slides[i].style.transition = "";
}
},
setTransition: function(speed) {
var swiper = this;
for (var i = 0; i < swiper.slides.length; i++) {
swiper.slides[i].style.transition = speed + "ms";
swiper.slides[i].querySelector(".slide-inner").style.transition =
speed + "ms";
}
}
}
};
var swiper = new Swiper(".swiper-container", swiperOptions);
I expect that slider plays the slides automatically and the autoplay stops when user clicks either next or previous button.
Use autoplay
autoplay: {
delay: 2500,
disableOnInteraction: true,
},
disableOnInteraction will disable autoplay when you will click on arrows.
From top of my head you can use something like this
let timer = null
const startSwiping = () => {
timer = setInterval(() => {
swiper.slideNext(100); // transition duration (ms) as argument
}, 500)
}
const stopSwiping = () => clearInterval(timer)
I did not try that but Swiper docs dont show any other option than swiper.slideNext(speed)

How to zoom in on image of current slick slide?

I have followed https://jsfiddle.net/mkbctrll/aq9Laaew/300936/ from #mkbctrl and managed to create a slick slider but i want to zoom in on the image of the current slide.How do i implement this?
I tried using photoswipe but it doesn't work.I tried using their example but it still did not work.Other methods only have zoom in temporarily meaning when is on hover on click,the image is zoomed but it moves around with the mouse.I do not want this.What i hope to achieve is the same as CSS Zoom.
//view selected images
const fileInput = document.getElementById('my_file');
const fileList = document.getElementById('imageContainer');
let slickSettings = {
infinite: true,
draggable: false,
arrows: true,
slidesToShow: 1,
slidesToScroll: 1,
swipe: false
};
let initSlickCarousel = (target, settings) => {
const $target = $(target);
switch (true) {
case $target.hasClass('slick-initialized'):
$target.slick('unslick');
$target.slick(settings);
break;
default:
$target.slick(settings);
}
};
const handleInputChange = (event) => {
console.log('We are handling it sir!');
const filesArray = Array.from(event.target.files);
if (filesArray.length > 20) {
alert("Please select 20 or less images!!");
} else {
filesArray.map((singleFile) => {
const outputImg = document.createElement('img');
const fileReader = new FileReader();
outputImg.className = 'img-thumbnail';
// Let's read it as data url - onload won't return a thing without it
fileReader.readAsDataURL(singleFile);
fileReader.onload = (event) => {
outputImg.src = event.target.result;
};
console.log(outputImg);
console.log(event.target.files);
fileList.appendChild(outputImg);
document.getElementById("openBtn").style.pointerEvents = "none";
});
var files = event.target.files;
for (var i = 0; i < files.length; i++) {
$("#files").append('<div class="filename"><span name="fileNameList">' + files[i].name + '</span></div>');
}
if ($(".imageContainer").hasClass("slick-initialized")) {
$('.imageContainer').on('beforeChange', function (event, slick, currentSlide, nextSlide) {
$(".imageContainer").slick('slickSetOption', 'adaptiveHeight', true, true);
});
}
initSlickCarousel(fileList, slickSettings);
}
};
When I click on a zoom in button,my image that is currently displayed should zoom in.What should I do to achieve this?
UPDATE:
I have managed to do the zoom by a comment recommendation but it doesnt show the full image.This is because i set the container for my slider to 200px for both width and height.How should I make the container responsive to the currently displayed image?If i set the width to auto,it will display all the images in the slides.

displaying images with different intervals

I am trying to display an array of images that will replace each other but will have 0.5 sec of black screen between them.
For example array_img = ["im1","im2","im3"]
show im1 for 3 seconds, show a black screen for 0.5 sec, show im2 for 3 sec, black screen for 0.5 sec...
my code is
images_array is a global variable.
time_between_images = 3000 and is a global var
time_for_black_screen = 500 and is a global var
function displayImages(){
for (i = 0; i < images_array.length; i++) {
console.log(images_array[i]);
setTimeout(function () {
console.log("1");
$('#exprBody').css('background-color', 'white');
$('#picture_frame').append(images_array[i]);
}, time_between_images)
setTimeout(function () {
console.log("2");
$("#picture_frame img:last-child").remove()
$('#exprBody').css('background-color', 'black');
}, time_for_black_screen)
}
console.log(images_array);
}
My html is simple
<body>
<div id="experiment_frame">
<div id="header">
<div id="left_cat"></div><div id="right_cat"></div>
</div>
<div id="picture_frame" class="centered">
<div id="exp_instruct"></div>
</div>
</div>
</body>
I thought I should use setTimeout because I want to switch between the two tasks for each image in the array. IMG + black screen, IMG + black screen
but nothing is showing on my page.
The array
["<img src='/images/cute/1223.jpg'>", "<img src='/images/cute/1235.jpg'>", "<img src='/images/disgusted/8878.jpg'>", "<img src='/images/disgusted/8898.jpg'>", "<img src='/images/neutral/3321.png'>", "<img src='/images/neutral/3445.png'>"]
EDIT:
function displayImageIter(number) {
$("body").toggleClass("whiteBody");
$("#picture_frame").empty().append(images_array[number]);
setTimeout(function () {
$("#picture_frame").empty();
$("body").toggleClass("blackBody");
setTimeout(function () {
displayImageIter((number + 1) % images_array.length);
}, time_for_black_screen)
}, time_between_images)
}
For some reason what I have with this code is
IMG white background
blank screen white for 0.5 sec
IMG black background
blank screen white for 0.5 sec
Take a look at the following simplified code:
function displayImage(number) {
$("#picture_frame").empty().append(images_array[number]).css('background-color', 'white');
setTimeout(function () {
$("#picture_frame").empty().css('background-color', 'black');
setTimeout(function () {
displayImage((number + 1) % images_array.length);
}, time_for_black_screen)
}, time_between_images)
}
const images_array = ["FIRST IMAGE", "SECOND IMAGE", "THIRD IMAGE"];
const time_between_images = 3000;
const time_for_black_screen = 500;
displayImage(0);
#picture_frame {width: 200px; height: 100px; background-color: white}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="picture_frame"></div>
Answering your comment:
a) To change the parent div background just use:
$("#picture_frame").empty().append(images_array[number]);
$("#parent_div").css('background-color', 'white');
b) To stop this loop you could use some flag and set it to false after 15 minutes:
function displayImage(number) {
$("#picture_frame").empty().append(images_array[number]).css('background-color', 'white');
setTimeout(function () {
$("#picture_frame").empty().css('background-color', 'black');
setTimeout(function () {
//continue only if flag is true
if (flag) displayImage((number + 1) % images_array.length);
}, time_for_black_screen)
}, time_between_images)
}
const images_array = ["FIRST IMAGE", "SECOND IMAGE", "THIRD IMAGE"];
const time_between_images = 3000;
const time_for_black_screen = 500;
let flag = true;
displayImage(0);
//used 15 seconds here for simplicity
setTimeout(() => flag = false, 15000);
<a class="img0"></a>
<a class="img1"></a>
<a class="img2"></a>
<script>
var elArr = [];
// here get the els that you wanna to display img
elArr.push(document.querySelector('.img0'));
elArr.push(document.querySelector('.img1'));
elArr.push(document.querySelector('.img2'));
// here is the img src and it's interval
var imgArr = [
{ src: 'a', interval: 3000 },
{ src: 'b', interval: 2000 },
{ src: 'c', interval: 1000 },
];
// if you wanna auto display unknown number imgs, set a variable to count the whole interval
var sumInterval = 0;
for (var i = 0; i < imgArr.length; i++) {
// the first interval is 3000, second is 5000, third is 6000
sumInterval += imgArr[i].interval;
setTimeout(function () {
elArr[i].innerHTML = imgArr[i].src;
// if is the last img, set an interval and then do the callback
if (i == imgArr.length - 1) {
setTimeout(function () {
alert('now screen shall be black');
}, 500)
}
}, sumInterval);
}
</script>

Categories

Resources