0

I'm very inexperienced with javascript and coding in general, so I've cobbled the below code together from a few examples I've seen here and elsewhere. The code does 90% of what I want now, but I am wondering how I can get it to display the first image in the array "videoFrames" before the slider is moved, as it is right now, the canvas is blank on page load. The second thing is that it doesn't seem like the images are loading on page load, am I wrong? How can I improve/incorporate that into this code?

var canvas = document.getElementById("myCanvas");
var width = 1920;
var height = 1080;

var totalImages = 50;
var videoFrames = [];
for (var i = 1; i <= totalImages; i++) {
  var img = new Image;
  var videoFrameUrl = 'https://the-faction.squarespace.com/assets/chalet-lightmix-01/Chalet-V06_LM_' + i + '.jpg';
  img.src = videoFrameUrl;
  videoFrames.push(img);
}

$("#my-input").on("input", function(event) {
  var currentImage = videoFrames[event.target.value - 1];
  var ctx = canvas.getContext("2d");
  ctx.drawImage(currentImage, 0, 0, canvas.width, canvas.height);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="myCanvas" width="400" height="225" style="border:1px solid #d3d3d3;">
    Your browser does not support the HTML5 canvas tag.
    </canvas>
<br>
<input id="my-input" type="range" min="1" max="50" value="0" />
mplungjan
  • 169,008
  • 28
  • 173
  • 236

3 Answers3

2

I get 404 on some of your images, but I would try triggering the input.

You can indeed draw immediately

Loading an image onto a canvas with javaScript

instead of waiting for input, but it will be the same wait as triggering it since the canvas has to exist before drawing on it

Here is how to trigger - NOTE:if the images are large, the preload of the the first image may have not yet finished when you trigger it

var canvas = document.getElementById("myCanvas");
var width = 1920;
var height = 1080;

var totalImages = 50;
var videoFrames = [];
for (var i = 1; i <= totalImages; i++) {
  var img = new Image;
  var videoFrameUrl = 'https://the-faction.squarespace.com/assets/chalet-lightmix-01/Chalet-V06_LM_' + i + '.jpg';
  img.src = videoFrameUrl;
  videoFrames.push(img);
}

$("#my-input").on("input", function(event) {
  var currentImage = videoFrames[event.target.value - 1];
  console.log("val",currentImage)
  var ctx = canvas.getContext("2d");
  ctx.drawImage(currentImage, 0, 0, canvas.width, canvas.height);
}).trigger("input");
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="myCanvas" width="400" height="225" style="border:1px solid #d3d3d3;">
    Your browser does not support the HTML5 canvas tag.
    </canvas>
<br>
<input id="my-input" type="range" min="1" max="50" value="0" />

Version that may be working better

var canvas,ctx;
$(function() {
  canvas=$("#myCanvas")[0];
  ctx = canvas.getContext("2d");
  $("#my-input").on("input", function(event) {
    var currentImage = videoFrames[this.value];
    ctx.drawImage(currentImage, 0, 0, canvas.width, canvas.height);
  })

  var totalImages = 50;
  var videoFrames = [];
  for (var i = 1; i <= totalImages; i++) {
    videoFrames[i] = new Image();
    if (i === 1) {
      videoFrames[i].onload = function() {
        console.log("loaded")
        $("#my-input").trigger("input");
      }
    }
    videoFrames[i].src = 'https://the-faction.squarespace.com/assets/chalet-lightmix-01/Chalet-V06_LM_' + i + '.jpg';
  }
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="myCanvas" width="400" height="225" style="border:1px solid #d3d3d3;">
    Your browser does not support the HTML5 canvas tag.
    </canvas>
<br>
<input id="my-input" type="range" min="1" max="50" value="1" />
mplungjan
  • 169,008
  • 28
  • 173
  • 236
  • can you actually draw an image to canvas before it's been loaded? in other words will it be drawn as soon as it's loaded? – Manuel Otto Nov 07 '17 at 08:34
  • 2
    @ManuelOtto no, the data needs to be buffered to the `Image()` instance before `drawImage()` is allowed to be invoked, otherwise it won't work. – Patrick Roberts Nov 07 '17 at 08:35
  • You can load an image onto the canvas before the user has made an input but it will be the same wait as triggering the input – mplungjan Nov 07 '17 at 08:37
  • Thanks mplungjan, the input trigger seems to work on jsfiddle, but on my actual site, it doesn't seem to? Can you tell me if you're experiencing the same? [Site Example](https://thefactioncgi.com/vr-interactive) I also noticed that the images seem to load miles faster in jsfiddle than they do on my site, maybe squarespace has some weird rules slowing it down? – Dippndots Nov 07 '17 at 08:46
  • I wonder why this answer got 2 upvotes: You are basically forcing the drawing before the images has loaded. If it weren't cached, it would fail and even throw an NSError in FF. – Kaiido Nov 07 '17 at 08:46
  • Kaiido, is the answer here to add a loading graphic of some kind? – Dippndots Nov 07 '17 at 08:49
  • @Dippndots, sorry I didn't get it – Kaiido Nov 07 '17 at 08:50
  • Yeah - you may run into images not loaded when running unless you pre-preload the first image before you trigger the frame – mplungjan Nov 07 '17 at 08:52
  • Yeah the input trigger doesn't work on my site for some reason, in order to add a "preview image" would I use another set of `img = new Image` and `drawImage` before my input event functions, like the link you put? – Dippndots Nov 07 '17 at 09:09
  • Please have a look at my second solution. It should work – mplungjan Nov 07 '17 at 09:13
  • 1
    Anyone notice you only need 5 images and you just fade between them. Why load 50 image???? – Blindman67 Nov 07 '17 at 09:19
  • @mplungjan Thanks, that definitely works better, after a second of loading, the first image in the array fills the canvas. Though it feels like it is taking too long to load all the images, it is only 5.5mb... I think i need to look into adding a loading graphic and code to check if the `videoFrames` array is full. – Dippndots Nov 07 '17 at 09:22
  • @Blindman67 that could be a solution for this particular case, but I definitely have other instances where I would need every frame from the animation – Dippndots Nov 07 '17 at 09:23
0

What you seem to want is an image preloader.

The basic rule when dealing with images is You can't do anything with it before it has loaded.

You may implement all kind of image preloaders, depending on your rules, here is one which will trigger a callback when all the images have loaded an other one when the first image has loaded and which will remove the ones that did fail from the stack:

function preload(imgURLs, onalldone, onfirstdone){
  var loaded = 0;
  function onsingleload(){
    if(++loaded >= imgURLs.length){
      onalldone(stack);
    }
  }
  function onerror(evt){
    // remove the image from the stack
    stack.splice(stack.indexOf(evt.target), 1);
    onsingleload();
  }
  var stack = imgURLs.map(function(url, i){
    var img = new Image();
    if(!i && typeof onfirstdone === 'function'){
      img.addEventListener('load', onfirstdone);
    }
    img.src = url;
    img.onload = onsingleload;
    img.onerror = onerror;
    return img;
  });
  return stack;
}

// now your code
var canvas = document.getElementById("myCanvas");
var width = 1920;
var height = 1080;

var totalImages = 25;
var videoFrames = [];
for (var i = 1; i <= totalImages; i++) {
  videoFrames.push('https://the-faction.squarespace.com/assets/chalet-lightmix-01/Chalet-V06_LM_' + i + '.jpg');
}

videoFrames = preload(videoFrames, 
// when all images have loaded
function onfullyloaded(loadedImages){
  $("#my-input").attr({
    max: loadedImages.length - 1, // some errors ?
    disabled: false // now we can enable the range
    });
},
// when the first of the stack has loaded
function onfirstimageloaded(event){
    var ctx = canvas.getContext("2d");
    ctx.drawImage(event.target, 0, 0, canvas.width, canvas.height);
  });

$("#my-input").on("input", function(event){
  var currentImage = videoFrames[event.target.value];
  var ctx = canvas.getContext("2d");
  ctx.drawImage(currentImage, 0, 0, canvas.width, canvas.height);
  })
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="myCanvas" width="400" height="225" style="border:1px solid #d3d3d3;">
    Your browser does not support the HTML5 canvas tag.
</canvas>
<br>
<input id="my-input" type="range" min="0" max="50" value="0" disabled/>
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • Thanks Kaiido, this also seems like a good solution, I suppose all I would add is some sort of loading notification until the slider is enabled again. Also, could you shed some light on loading times? It seems to be a consistent problem, my internet isn't the fastest, but like I've said before, all the images only total ~3.5MB, yet it takes a lot longer than it should to download that... – Dippndots Nov 07 '17 at 10:46
0

Wow 50 HD images, thats about 0.4Gig of RAM. Maybe you should consider compressing it all into a video.

Anyways don't load all the images in one go, start the first landing image as soon as you can. Use the image onload event to render it to the canvas as soon as it is ready.

Then load every 8th image so that you can at least fake the animation, people are not going to hang around waiting for 50 images to load, so give them something early. When every 8th image has loaded then get the ones in between.

As you are loading and the slider is moved, use the canvas to fade between the images you have.

The example below just uses 6 images. But it will work for any number of images (if the animation needs it) But you need to load them in steps, every so many, then the ones in between, and so on till all have loaded. While you wait fade between them if the slider is moved.

const ctx = canvas.getContext("2d");


const imagesIdx = [1,10,20,30,40,50];
const images = [];
imagesIdx.forEach((idx,i) => {
    const img = new Image;
    img.src = "https://the-faction.squarespace.com/assets/chalet-lightmix-01/Chalet-V06_LM_" + idx + ".jpg";
    if (i === 0) { img.onload = loadedFirst }
    images.push(img);
});

function loadedFirst(){

    ctx.drawImage(images[0],0,0,canvas.width, canvas.height);
    slider.addEventListener("mousemove",sliderMove);
}
var sliderValOld;
function sliderMove(){
    if(slider.value !== sliderValOld){
      var imageA = (Number(slider.value) / (Number(slider.max) / images.length)) | 0;
      if(imageA >= images.length){
          imageA -= 1;
      }
      var imageB = imageA + 1;

      while(!images[imageA].complete){ imageA -= 1 }
      while(imageB < images.length && !images[imageB].complete) { imageB += 1 }
      ctx.globalAlpha = 1;
      ctx.drawImage(images[imageA],0,0,canvas.width, canvas.height);
      if(imageB < images.length){
        var rng = Number(slider.max) / images.length;
        var a = imageA * rng;
        var b = imageB * rng;
        ctx.globalAlpha = (Number(slider.value) - a) / (b - a);
        ctx.drawImage(images[imageB],0,0,canvas.width, canvas.height);
      }
    }
    sliderValOld = slider.value;
}
<input id="slider" type="range" min="0" max="600" value="0" /><br>
<canvas id="canvas" width="400" height="225" style="border:1px solid #d3d3d3;">
  </canvas>
Blindman67
  • 51,134
  • 11
  • 73
  • 136
  • wow thanks @Blindman67, this is definitely a creative solution. One issue I have is that all subsequent images are drawn to the canvas at 400x225, instead of following the first image's size. I'm not fluent enough to unpack where in your code this is occurring. Also, is it really .4GB of RAM? the entire folder of images was ~5.5MB (i've since resized them to 720 and now all the images are ~3.47MB) – Dippndots Nov 07 '17 at 10:41
  • @Dippndots Each image 1920 * 1080 is about 2Mp, each pixel is 32Bits (4 bytes) 2 * 4 * 50 = 400Mb (0.4Gb). Size on disk does not equal size in RAM. You can set the canvas size to whatever you want (I was being lazy) and replace the drawImage i have with `ctx.drawImage(images[imageA], 0, 0, width ,height);` (as your original variables). I made a slight mistake in the code, when I fix it I will change the size to match your example. BTW did you model and render the scene? – Blindman67 Nov 07 '17 at 14:06
  • thanks, I thought web-format images were only 8-bit though? Yes me and my team of 3 others modelled and rendered the scene, we just started and since i'm the "best" at coding, I got roped into doing the website lol. – Dippndots Nov 07 '17 at 15:11