skip to Main Content

the 2 promises are:

  1. loading the next image Load_Img_p.
  2. 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?

[edit] adding 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


  1. Chosen as BEST ANSWER

    The solution I use...

    the promise Load_Img_p has become:

    Load_Img_p = (imgScr ) => new Promise( (resolve ) => 
      {
      imgOnLoad   = true;
      const img   = new Image();
      img.src     = imgScr;
      img.decode()
      .then(()  => { resolve( img );  })
      .catch(() => { resolve( null ); });
      })
    

    but the whole thing cannot work without await small Delay(); in the final line in the while() 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:

    .then(()  => { resolve( img );  })
    

    In:

    .then(()  => { setInterval(() => { resolve( img ) }, 10000)  })
    

    to make the Hourglass animation appear flawlessly. ;)

    This question remains open...

    const 
      img_box = document.querySelector('#img-box')
    , imgs_src_list =
        [ 'https://picsum.photos/id/124/400/300'
        , 'https://picsum.photos/id/146/400/300'
        , 'https://picsum.photos/id/155/400/300'
        , 'https://picsum.photos/id/219/400/300'
        ];
    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.src     = imgScr;
        img.decode()
        .then(()  => { resolve( img );  })
        .catch(() => { resolve( null ); });
        })
    , 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 [_,newImg] = await Promise.all([ Timing_Anim_p() , Load_Img_p( nxt_src ) ]);
    
        Hourglass_f(false);
        imgOnLoad = false; 
    
        if (!chkBxPlayer.checked) break;
     
        if (!!newImg)
          {
          img_index = nxt_imgIdx;
          img_box.replaceChildren( newImg );
          }
        else
          {
          chkBxPlayer.checked = false;
          console.log('Image loading error', nxt_src );        
          }
        await smallDelay();
        }
      Hourglass_f(false); // defensive programming...
      }
    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" alt="">
    </div>
    <svg id="hourglass" viewbox="0 0 220 220" class="" >
      <circle cx="110" cy="110" r="50" stroke-width="10" fill="transparent" />
    </svg>
    <div id="Timing-Anim-bar"></div>
    <br>
    <label> <input type="checkbox" id="chkBx-player" > play diaporama </label>


  2. 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.

    setTimeout(() => {
     Timing_Anim_e.classList.add('showTiming')
    }, 0);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search