jackieeeeeeeeeee jackieeeeeeeeeee - 4 months ago 21x
jQuery Question

Vimeo images and javascript

So im having trouble getting my thumbnail system to work. It gets a image from vimeo and puts it in a . So i tried to make it loop, because that looks nicer.

But i cant get it to work :( Can somebody help me?

<div class="row">
<div class="col-sm-3"><img src="" id="img1" class="img-rounded" width="100%" height="200px"></img></div>
<div class="col-sm-3"><img src="" id="img2" class="img-rounded" width="100%" height="200px"></img></div>
<div class="col-sm-3"><img src="" id="img3" class="img-rounded" width="100%" height="200px"></img></div>
<div class="col-sm-3"><img src="" id="img4" class="img-rounded" width="100%" height="200px"></img></div>
<div class="row">
<div class="col-sm-3"><img src="" id="img5" class="img-rounded" width="100%" height="200px"></img></div>
<div class="col-sm-3"><img src="" id="img6" class="img-rounded" width="100%" height="200px"></img></div>
<div class="col-sm-3"><img src="" id="img7" class="img-rounded" width="100%" height="200px"></img></div>
<div class="col-sm-3"><img src="" id="img8" class="img-rounded" width="100%" height="200px"></img></div>

var ids = ["169971394", "169657641", "169569693", "169569661", "169569619", "169569539", "169569509", "169566439"]
var imgs= ["img1", "img2", "img3", "img4", "img5", "img6", "img7", "img8"]
for (i = 0; i < 7; i++) {
$(document).ready(function () {
var vimeoVideoUrl = 'https://player.vimeo.com/video/' + ids[i];
var match = /vimeo.*\/(\d+)/i.exec(vimeoVideoUrl);
if (match) {
var vimeoVideoID = match[1];
$.getJSON('http://www.vimeo.com/api/v2/video/' + vimeoVideoID + '.json?callback=?', { format: "json" }, function (data) {
featuredImg = data[0].thumbnail_large;
$(imgs[i]).attr("src", featuredImg);

(Above is the important part.) FYI: im using bootstrap and jquery for this. (And some other things, but those are not important)


Some Background

The complication with doing this sort of thing in a loop is that $.getJSON() is asynchronous, and for loops are synchronous. For a basic understanding of the difference, check out this answer. But here's the general idea:

Synchronous: Things are executed in sequence. When one operation finishes, the next one starts.


var a = 0; // This is executed first
a = a + 1; // This is executed second

Asynchronous: The synchronous operations do not wait for asynchronous operations to complete before continuing with the script


// This is executed first
var a = 0;

// This is executed 2nd, but the function inside won't be called for 1 second
setTimeout(function() {
  // this is executed last, outputs 1
}, 1000);

// this is executed third, before the function call
a = a + 1;

Your Problem

In your example code, you are executing an asynchronous function inside of a synchronous operation. Normally, this is okay, and you don't really have to worry about it. The issue arises when you try to use the variable i in your asynchronous function, but the variable i is changed during your synchronous operation (the for loop).

Here is an example to display what is happening

for (var i = 0; i < 5; i++) {
  setTimeout(function() {
  }, 1000)

Thinking about it

Now, by looking at the code you might expect it to wait 1 second, then log each number to the console, something like

-> 0
-> 1
-> 2
-> 3
-> 4

The Catch

However, if you actually run the snippet above, you will see that it logs 5 every time. This is because the variable i actually has a value of 5 when the callback functions actually run.

But how?

How? The for loop continued execution before each setTimout() finished it's 1 second wait. So by the time that 1 second was over, the loop had already incremented i to the point that broke the loop, which was 5 in this case.

In your problem, a simple console.log(i); during your callback function will most likely show 7 eight times because the variable i is still being incremented while your $.getJSON() is actually waiting for the results to be returned. When the results actually are returned, the loop is probably done running, and i will equal 7

The Solution

In order to be sure that we are putting the videos and images in the correct order on the page and that we are actually putting each image in the right place, we need to do some hefty workarounds for this asynchronous behavior. I've commented this code to give you an idea of what each line is doing.

// dom is ready to be manipulated
$(document).ready(function() {
  // create an array of our video ids
  var ids = [
  // initialize an empty array that will eventually hold our videos
  var videos = [];
  // loop through our array of ids
  ids.forEach(function(id, i) {
    // because these are in order, ids[0] should be in #img1 etc
    var image = '#img' + (i + 1);

    // get your video url --not actually used in this code
    var vimeoVideoUrl = 'https://player.vimeo.com/video/' + id;

    // create an object that holds the information for this video that can
    // be gathered *synchronously*
    var v = {
      id: id,
      image: '#img' + (i + 1),
      video: vimeoVideoUrl

    // add this object to our initially empty array

    /* after the loop ends, videos array might look like this

       id: "169971394",
       image: "#img1",
       video: "https://player.vimeo.com/video/169971394"
       ... next video
     } ... for each video

    // do our ajax operation to get the rest of the video information
      'https://www.vimeo.com/api/v2/video/' + id + '.json?callback=?', {
        format: "json"
      // REMEMBER -- this function is *asynchronous* and we can't trust the
      // value of the increment from our loop, so this is relatively complex
      function(data) {
        // get the id of the returned video
        var id = data[0].id;

        // get the thumbnail
        var thumb = data[0].thumbnail_large;

        // loop through our videos array
        videos.forEach(function(v, i) {
          // find the element of the array with the id that was just returned
          if (v.id == id) {
            // and add the thumbnail url to the object
            videos[i].thumbnail = thumb;

            /* an element of the videos array will look like this when the callback
               has finished for that video

              id: "169971394",
              image: "#img1",
              video: "https://player.vimeo.com/video/169971394",
              thumbnail: "https://i.vimeocdn.com/video/574879586_640.jpg"

              notice that the thumbnail property is now set

        // each time we execute the callback, we need to know if it's the last 
        // execution, so we will need to check if every callback has been completed

        // this condition returns false if the thumbnail element is not assigned
        // to every object in the videos array, which would tell us that this isn't
        // the last execution of the callback
        if (videos.every(function(v) {
          return v.thumbnail !== undefined
        })) {
          // if this is the last execution of the callback,
          // loop through each item in the videos array
          videos.forEach(function(video) {
            // set the src of the video's corresponding image to the url of the thumbnail
            $(video.image).attr("src", video.thumbnail);

https://jsfiddle.net/o82L6q48/2/ to see this in action

Note that this may be more complicated than it actually needs to be, but it works, and I hope that you learn something from it.