Curriculum: page 3, moving pictures

You’ve written your HTML and CSS. We got a taste of how to use JavaScript to move around the content. Now, we’ll learn how to include your program when the HTML is loaded, so we can make the content dynamic and interactive.

Including a JavaScript file

Goal

To see how to set up a JavaScript environment.

Instructions

In your editor, create a new file and save it with the name “slideshow.js” in your slideshow directory.

In your slideshow.js, add the line that creates a reference to film roll:

var filmroll = document.getElementById('the-film-roll');

In your index.html, include this new file at the very end:

<script src="slideshow.js"></script>

Back in the browser, refresh the page. Type the following in the console:

filmroll.style.left = '-100px';

Explanation

Whoa! Just like images and style sheets, you can load JavaScript code when you load a web page. Instead of typing by hand on the console, we can prepare a program, and send it along with the HTML, CSS or images. Nice!

Create a function to move the film roll

Goal

To use setInterval to create a simple animation.

Instructions

Yesterday we saw that setInterval could be used to run a function every n milliseconds. We can also stop the function using clearInterval. Update your slideshow.js file to contain this code, and then refresh the browser:

var filmroll = document.getElementById('the-film-roll');
var start = 0;
var end = 200;
var current = start;

function move() {
  filmroll.style.left = current + 'px';
  current = current + 1;
  if (current > end) {
    clearInterval(loop); // This stops the loop
  }
}

var loop = setInterval(move, 40);

Why can’t we use a for or while loop for this effect?

Try to speed up the scrolling effect. There are two ways. Which one works better?

Update the move function to move the film roll 20 pixels every 50 milliseconds.

What if you want to move exactly 110 pixels? Update your function so that it goes to exactly the end and not more.

Tip: Math.min is a function that takes any number of arguments, and returns the one that is smallest. Try this:

current = Math.min(current + 20, end);

Make the photos scroll to the left instead of right.

How much do you have to move the film roll to make it scroll from the first photo to the second photo?

What happens if your photo width is not a multiple of 20? How can you fix the problem?

Update the move function so that the direction of scrolling is only controlled by changing the start and end values.

Explanation

To control how fast or slow a loop is performed, we can change the time between loops, or we can change how big an effect each loop has.

We used to use setInterval instead of a while (or for) loop so we could add time between the loops instead of doing many thousands and thousands of loops.

In the end, your function might look something like this:

function move() {
  filmroll.style.left = current + 'px';

  if (current == end) {
    clearInterval(loop);
  }

  if (end > start) {
    current = Math.min(current + 20, end);
  } else {
    current = Math.max(current - 20, end);
  }
}

By using the Math.min and Max.max functions, we can guarantee that the current value never goes beyond the end (either bigger or smaller, depending on direction)

Create a scroll function

Goal

Create a re-usable scroll function that can be called multiple times.

Instructions

Create a function called scroll that accepts a start position, and an end position.

function scroll(start, end) {
  // now what?
}

Move the current counter, your move function and setInterval inside of scroll. You no longer need to define start and end variables. Your slideshow.js file will look like this:

var filmroll = document.getElementById('the-film-roll');

function scroll(start, end) {
  var current = start;

  function move() {
    filmroll.style.left = current + 'px';

    if (current == end) {
      clearInterval(loop);
    }

    if (end > start) {
      current = Math.min(current + 20, end);
    } else {
      current = Math.max(current - 20, end);
    }
  }

  var loop = setInterval(move, 50);
}

Refresh the browser.

Use the scroll function to slide to the second photo by typing in the console type:

scroll(0, -500);

(… but replace 500 with the width of one of your photos.)

Use the scroll function to slide from the second photo to the third.

Scroll back to the second photo.

Explanation

Now we have a single function we can use to scroll left and right from any starting position to any ending position. Yay!

Add a keydown event listener

Goal

To see that we can give control of the slide show to the user.

Instructions

In slideshow.js, add an EventListener called handleEvent to the document that triggers on a keydown event, like this:

function handleEvent(e) {
  console.log(e, e.keyCode);
}

document.addEventListener('keydown', handleEvent);

In Chrome, be sure you have the Console tab open in the Developer Tools.

Refresh the page.

Begin pressing any of the keys, and while you do, watch the output that is logged in the console.

What are the keyCodes for the left and right arrow keys?

Update the handleEvent function to call a function that scrolls left or right when the left or right arrow is pressed:

function handleEvent(e) {
  backAndForth(e.keyCode);
}

function backAndForth(keyCode) {
  if (keyCode == 37) {
    scroll(500, 0);
  }

  if (keyCode == 39) {
    scroll(0, 500);
  }
}

(… but replace 500 with the width of one of your photos.)

Try out this new functionality in Chrome. Remember to always refresh the page when you change your JavaScript code.

Why doesn’t it keep scrolling in the same direction when you press the same arrow?

Store the current scrolling position in a variable, and after calling the scroll function, update the value of the current position. Always use the current position as the start value when you call the scroll function.

Try it first! But if you need help, take a peek below:

var current = 0;

function backAndForth(keyCode) {
  if (keyCode == 37) {
    scroll(current, current - 500);
    current = current - 500;
  }

  if (keyCode == 39) {
    scroll(current, current + 500);
    current = current + 500;
  }
}

Explanation

When JavaScript runs in your browser (which is all the time, unless you shut off JavaScript), it is always “listening” to events, like whether you type something, or move your mouse. These events have names like “keydown”, “keyup”, and “click”, and are emitted by all kinds of elements in the document, and by the document itself. In this example, we are only listening to keydown events emitted by the document.

By listening to the events emitted, we can know when the person viewing the page has interacted with the page. We can look at what the person did (what they typed, or what they clicked on) and then we can do something. In this example, we moved the film roll to the left when the person pressed “left” and we moved it right when the person pressed “right”.

By listening to input from the person looking at the page, we can allow the person to control the page.

Pretty cool! And very powerful.

Correct buggy behaviour

Goal

To see how to prevent events from having an effect when it is not desirable.

Instructions

Our functions for listening to events and scrolling have a bug in them. Maybe you already noticed it. To recreate the bug, try pressing the left and right arrows faster than the film roll can slide. (So, start the film roll sliding, and then press either left or right again before it is finished sliding). You’ll see an ugly flickering effect.

Do you know what is causing it? What can we do to stop it?

Try to fix this bug.

Explanation

Lots of times when we write code, we forget that our users might do things that don’t “make sense” at first, or will try to do things we didn’t think of. Oops. We think our job is done, but then we find out our code has a bug and we have to correct it (or tell the users “Don’t do that!” which is sort of rude).

First we have to figure out that the bug exists. Then we have to figure out why the bug is happening. Then we have to figure out how to fix it. Lots of programmers think fixing bugs is fun :), since they are new puzzles to solve.

Did you find a solution? This is one way to fix the bug:

var scrolling = false;

function scroll(start, end) {
  var current = start;

  var move = function () {
    filmroll.style.left = current + 'px';

    if (current == end) {
      clearInterval(loop);
      scrolling = false;
    }

    if (end > start) {
      current = Math.min(current + 20, end);
    } else {
      current = Math.max(current - 20, end);
    }
  }

  var loop = setInterval(move, 50);
  scrolling = true;
}

function handleEvent(e) {
  if (!scrolling) {
    backAndForth(e.keyCode);
  }
}

We created a variable that remembers whether or not we are already scrolling, and if we are, we ignore the keydown event from the user.

You will notice that this solution uses an exclamation mark in the if (!scrolling) conditional statement (inside the handleEvent function). The exclamation mark is called the logical NOT operator in JavaScript, so the if (!scrolling) conditional statement has the meaning "if the scrolling variable is not true".

Maybe this solution to the problem does not fit with what the user expects. What is reasonable for the user to expect? How can we change the code to do what the user expects?

Next!

Now that we’ve changed CSS, let’s change the HTML too!

→ To the fourth page.