the 2 promises are:
- loading the next image
Load_Img_p
. - a waiting animation before moving to the next image
Timing_Anim_p
.
It also has a visual animation encouraging you to wait while waiting for the next image to fully load Hourglass_f
.
the idea is this:
execute in parallel Timing_Anim_p
and Load_Img_p
.
in the case of Timing_Anim_p
before Load_Img_p
is resolved, show Wait_Img_Load
this is done by a simple flag named imgOnLoad
code for Load_Img_p
var imgOnLoad = false;
const Load_Img_p = (imgScr ) => new Promise( (resolve ) =>
{
imgOnLoad = true;
const img = new Image();
img.onload =()=>{ imgOnLoad = false; resolve( img ) };
img.onerror =()=>{ imgOnLoad = false; resolve( null ) };
img.src = imgScr;
})
in situ code test for Hourglass_f
and Timing_Anim_p
var imgOnLoad = true; // need also this one to reject other user commands on interface.
const
hourglass_elm = document.querySelector('#hourglass')
, Hourglass_f = (run=false) =>
{
hourglass_elm.classList.toggle('showHourglass', run);
}
, Timing_Anim_e = document.querySelector('#Timing-Anim-bar')
, Timing_Anim_p =()=> new Promise( (resolve ) =>
{
Timing_Anim_e.addEventListener('transitionend', ()=>
{
Timing_Anim_e.classList.remove('showTiming');
Hourglass_f(imgOnLoad);
resolve('Timing_Anim_p ended ok');
}
, { once: true }
);
Timing_Anim_e.classList.add('showTiming');
})
;
btn_test_A.onclick =_=> Hourglass_f(true);
btn_test_B.onclick =_=> Hourglass_f(false);
btn_test_C.onclick =_=> Timing_Anim_p();
body {
background-color: #03063a;
}
#hourglass {
visibility : hidden;
width : 100px;
height : 100px;
stroke : #ff146e;
stroke-dasharray : 80;
stroke-dashoffset : 200;
}
#hourglass.showHourglass {
visibility : visible;
animation : hourglassAnim 1400ms linear infinite alternate;
}
@keyframes hourglassAnim { to { stroke-dashoffset: 0; } }
#Timing-Anim-bar {
width : 500px;
height : 10px;
border : 1px #9acd32 solid;
position : relative;
}
#Timing-Anim-bar::before {
position : absolute;
display : block;
top : 0;
left : 0;
height : 100%;
width : 0%;
content : '';
background : #0cfa2c;
}
#Timing-Anim-bar.showTiming::before {
transition : width 3s linear;
width : 100%;
}
<svg id="hourglass" viewbox="0 0 200 200" class="" >
<circle id="c5" cx="100" cy="100" r="50" stroke-width="5" fill="transparent" />
</svg>
<div id="Timing-Anim-bar"></div>
<br><br>
<button id="btn_test_A">hourglass true</button>
<button id="btn_test_B">hourglass false</button>
<button id="btn_test_C">test Timing_Anim_p</button>
complete image change loop code, but without using promise.ALL:
const
img_box = document.querySelector('#img-box')
, imgs_src_list =
[ 'https://picsum.photos/id/124/400/300.webp'
, 'https://picsum.photos/id/146/400/300.webp'
, 'https://picsum.photos/id/155/400/300.webp'
, 'https://picsum.photos/id/219/400/300.webp'
];
var
imgOnLoad = false
, img_index = 0;
;
const
hourglass_elm = document.querySelector('#hourglass')
, Hourglass_f = (run=false) =>
{
hourglass_elm.classList.toggle('showHourglass', run);
}
, Timing_Anim_e = document.querySelector('#Timing-Anim-bar')
, Timing_Anim_p =()=> new Promise( (resolve ) =>
{
Timing_Anim_e.addEventListener('transitionend', ()=>
{
Timing_Anim_e.classList.remove('showTiming');
Hourglass_f(imgOnLoad);
resolve('Timing_Anim_p ended ok');
}
, { once: true }
);
Timing_Anim_e.classList.add('showTiming');
})
, Load_Img_p = (imgScr ) => new Promise( (resolve ) =>
{
imgOnLoad = true;
const img = new Image();
img.onload =()=>{ imgOnLoad = false; resolve( img ) };
img.onerror =()=>{ imgOnLoad = false; resolve( null ) };
img.src = imgScr;
})
, chkBxPlayer = document.querySelector('#chkBx-player')
;
chkBxPlayer.addEventListener('change',()=>
{
if (chkBxPlayer.checked)
{
carousel_Loop();
}
else
{
Hourglass_f(false);
Timing_Anim_e.classList.remove('showTiming');
}
})
async function carousel_Loop()
{
let nxt_src, nxt_imgIdx = img_index
;
while (chkBxPlayer.checked)
{
nxt_imgIdx = ++nxt_imgIdx % imgs_src_list.length;
nxt_src = imgs_src_list[nxt_imgIdx];
await Timing_Anim_p();
if (!chkBxPlayer.checked) break;
Hourglass_f(true);
let newImg = await Load_Img_p( nxt_src );
Hourglass_f(false);
if (!chkBxPlayer.checked) break;
if (newImg===null)
{
chkBxPlayer.checked = false;
console.log('Image loading error', nxt_src );
break;
}
img_index = nxt_imgIdx;
img_box.replaceChildren( newImg );
}
Hourglass_f(false); // some security...
}
body {
background-color: #03063a;
}
#img-box, label {
margin : 20px;
border : 1px whitesmoke solid;
padding : 10px;
width : fit-content;
height : fit-content;
background : lightgrey;
border-radius : 5px;
position : relative
}
#hourglass {
position : absolute;
top : 50px;
left : 50px;
visibility : hidden;
width : 100px;
height : 100px;
stroke : #ff146e;
stroke-dasharray : 80;
stroke-dashoffset : 200;
}
#hourglass.showHourglass {
visibility : visible;
animation : hourglassAnim 1400ms linear infinite alternate;
}
@keyframes hourglassAnim { to { stroke-dashoffset: 0; } }
#Timing-Anim-bar {
width : 500px;
height : 10px;
border : 1px #9acd32 solid;
position : relative;
}
#Timing-Anim-bar::before {
position : absolute;
display : block;
top : 0;
left : 0;
height : 100%;
width : 0%;
content : '';
background : #0cfa2c;
}
#Timing-Anim-bar.showTiming::before {
transition : width 3s linear;
width : 100%;
}
<div id="img-box">
<img src="https://picsum.photos/id/124/400/300.webp" alt="">
<svg id="hourglass" viewbox="0 0 220 220" class="">
<circle cx="110" cy="110" r="50" stroke-width="10" fill="transparent" />
</svg>
</div>
<div id="Timing-Anim-bar"></div>
<br>
<label> <input type="checkbox" id="chkBx-player" > play diaporama </label>
What I tried, but the loop stops at the first change…
What’s wrong?
smallDelay()
as suggested by Terance Edmonds, which solved a part of this problem, because there is an overlap between the placement of the image and the start of the next loop. but this is more of a patch than a solution.
const
img_box = document.querySelector('#img-box')
, imgs_src_list =
[ 'https://picsum.photos/id/124/400/300.webp'
, 'https://picsum.photos/id/146/400/300.webp'
, 'https://picsum.photos/id/155/400/300.webp'
, 'https://picsum.photos/id/219/400/300.webp'
];
var
imgOnLoad = false
, img_index = 0;
;
const
smallDelay =_=>new Promise(r=>setTimeout(r,200))
, hourglass_elm = document.querySelector('#hourglass')
, Hourglass_f = (run=false) =>
{
hourglass_elm.classList.toggle('showHourglass', run);
}
, Timing_Anim_e = document.querySelector('#Timing-Anim-bar')
, Timing_Anim_p =()=> new Promise( (resolve ) =>
{
Timing_Anim_e.addEventListener('transitionend', ()=>
{
Timing_Anim_e.classList.remove('showTiming');
Hourglass_f(imgOnLoad);
resolve('Timing_Anim_p ended ok');
}
, { once: true }
);
Timing_Anim_e.classList.add('showTiming');
})
, Load_Img_p = (imgScr ) => new Promise( (resolve ) =>
{
imgOnLoad = true;
const img = new Image();
img.onload =()=>{ imgOnLoad = false; resolve( img ) };
img.onerror =()=>{ imgOnLoad = false; resolve( null ) };
img.src = imgScr;
})
, chkBxPlayer = document.querySelector('#chkBx-player')
;
chkBxPlayer.addEventListener('change',()=>
{
if (chkBxPlayer.checked)
{
carousel_Loop();
}
else
{
Hourglass_f(false);
Timing_Anim_e.classList.remove('showTiming');
}
})
async function carousel_Loop()
{
let nxt_src, nxt_imgIdx = img_index
;
while (chkBxPlayer.checked)
{
nxt_imgIdx = ++nxt_imgIdx % imgs_src_list.length;
nxt_src = imgs_src_list[nxt_imgIdx];
let resp = await Promise.all([ Timing_Anim_p() , Load_Img_p( nxt_src ) ]);
Hourglass_f(false);
let newImg = resp[1];
if (!chkBxPlayer.checked) break;
if (newImg===null)
{
chkBxPlayer.checked = false;
console.log('Image loading error', nxt_src );
break;
}
img_index = nxt_imgIdx;
img_box.replaceChildren( newImg );
await smallDelay(); //--- --- --- patch
}
Hourglass_f(false); // some security...
}
body {
background-color: #03063a;
}
#img-box, label {
margin : 20px;
border : 1px whitesmoke solid;
padding : 10px;
width : fit-content;
height : fit-content;
background : lightgrey;
border-radius : 5px;
position : relative
}
#hourglass {
position : absolute;
top : 50px;
left : 50px;
visibility : hidden;
width : 100px;
height : 100px;
stroke : #ff146e;
stroke-dasharray : 80;
stroke-dashoffset : 200;
}
#hourglass.showHourglass {
visibility : visible;
animation : hourglassAnim 1400ms linear infinite alternate;
}
@keyframes hourglassAnim { to { stroke-dashoffset: 0; } }
#Timing-Anim-bar {
width : 500px;
height : 10px;
border : 1px #9acd32 solid;
position : relative;
}
#Timing-Anim-bar::before {
position : absolute;
display : block;
top : 0;
left : 0;
height : 100%;
width : 0%;
content : '';
background : #0cfa2c;
}
#Timing-Anim-bar.showTiming::before {
transition : width 3s linear;
width : 100%;
}
<div id="img-box">
<img src="https://picsum.photos/id/124/400/300.webp" alt="">
<svg id="hourglass" viewbox="0 0 220 220" class="" >
<circle cx="110" cy="110" r="50" stroke-width="10" fill="transparent" />
</svg>
</div>
<div id="Timing-Anim-bar"></div>
<br>
<label> <input type="checkbox" id="chkBx-player" > play diaporama </label>
2
Answers
The solution I use...
the promise
Load_Img_p
has become:but the whole thing cannot work without
await small Delay();
in the final line in thewhile()
loop.Which leaves me skeptical...
for the controversy over the abundance of CSS code, I understand that this may weigh for some people, but I think it is really an important part in this question because it allows to check the correct progress of your graphic sequences: the timing, then the presence of the hourglass if the image is not completely loaded at the end of the timer.
To check for yourself, replace the line of code:
In:
to make the
Hourglass
animation appear flawlessly. ;)This question remains open...
Try replacing with the below code, that when the promises are executed, the ‘showTiming’ is executed just after the transition is ended so the animation won’t start over which leads only to one image being loaded.