Thursday 8 February 2024

Web Tutorial: Valentine's Day jQuery Animation (Part 1/2)

Hi and welcome, lovebirds!

Valentine's Day is coming up, and I'd like to play around with some jQuery animation. There will be no color animations, so no links to jQuery UI are required. Our objective today is to take a series of AI-generated Valentine-themed images, and transition through them randomly using a fancy animation. Sound good?

These are the images. Each is 500 by 750 pixels.

img0.jpg

img1.jpg

img2.jpg

img3.jpg

img4.jpg

img5.jpg

img6.jpg

img7.jpg

img8.jpg

img9.jpg

Here's some starting HTML with a link to jQuery. Background of the screen has been set to white.
<!DOCTYPE html>
<html>
  <head>
  <title>Happy Valentine's Day!</title>

  <style>
    body
    {
      background-color: rgb(255, 255, 255);
    }
  </style>

  <script src="https://code.jquery.com/jquery-3.7.0.js"></script>

  <script>

  </script>
  </head>

  <body>

  </body>
</html>


We will need a container div inside the HTML. Its id will be container. We will also add the images to the HTML for pre-loading.
<body>
  <div id="container">

  </div>

  <img src="img0.jpg" />
  <img src="img1.jpg" />
  <img src="img2.jpg" />
  <img src="img3.jpg" />
  <img src="img4.jpg" />
  <img src="img5.jpg" />
  <img src="img6.jpg" />
  <img src="img7.jpg" />
  <img src="img8.jpg" />
  <img src="img9.jpg" />

</body>


Here's the CSS. Width and height are the same as the image, and we use the margin property to align it to the middle of the screen. img tags are set to not display.
<style>
  body
  {
    background-color: rgb(255, 255, 255);
  }

  #container
  {
    width: 500px;
    height: 750px;
    margin: 10px auto 0 auto;
  }

  img
  {
    display: none;
  }

</style>


We want to fill the grid with smaller squares. So set the page to run the fillContainer() method of the vday object using the onload attribute.
<body onload="vday.fillContainer();">


And create the vday object in the script tag, with the fillContainer() method. While you're there, also create maxTransitionTime and squareSize properties. These are configuration settings that can be tweaked for performance.
<script>
  let vday =
  {
    maxTransitionTime: 10000,
    squareSize: 25,      
    fillContainer: function()
    {

    }
  }
</script>


We start by declaring the string imgURL, and calling getRandomImage() to assign a value to it.
fillContainer: function()
{
  var imgUrl = this.getRandomImage();
}


We'll then define the method getRandomImage(). All it really does is declare index as a random number between 0 and 9, and then return a string based on index. So it will be any one of the image file names we have.
<script>
  let vday =
  {
    maxTransitionTime: 10000,
    squareSize: 25,  
    getRandomImage: function()
    {
      var index = Math.floor(Math.random() * 9);

      return "'img" + index + ".jpg'";
    },
    fillContainer: function()
    {
      var imgUrl = this.getRandomImage();
    }
  }
</script>


We create a For loop to traverse through the number of rows in the container div, which is basically the height of container divided by squareSize. So what's going to happen here is that we create row as a div, style it using the CSS class row and set the style to have the height property at squareSize pixels. Append it inside container.
fillContainer: function()
{
  var imgUrl = this.getRandomImage();

  for(var i = 0; i < 750 / this.squareSize; i++)
  {
    var row = $("<div></div>");
    row.addClass("row");
    row.attr("style", "height:" + this.squareSize + "px");

    $("#container").append(row);
  }
}


Here's the CSS class row. Full width, and a red outline temporarily, for visibility.
#container
{
  width: 500px;
  height: 750px;
  margin: 10px auto 0 auto;
}

.row
{
  width: 100%;
  outline: 1px solid red;
}


img
{
  display: none;
}


So far so good...



Now we are going to fill in squares. Have another For loop within the first one, this time traversing the number of squares per row. This will be the width of container (500 pixels) divided by squareSize.
fillContainer: function()
{
  var imgUrl = this.getRandomImage();

  for(var i = 0; i < 750 / this.squareSize; i++)
  {
    var row = $("<div></div>");
    row.addClass("row");
    row.attr("style", "height:" + this.squareSize + "px");

    for(var j = 0; j < 500 / this.squareSize; j++)
    {

    }

    $("#container").append(row);
  }
}


In here, create a div, style it using the square CSS class, and make sure both height and width are squareSize pixels. And then append it to row.
for(var j = 0; j < 500 / this.squareSize; j++)
{
  var square = $("<div></div>");
  square.addClass("square");
  square.attr("style", "height:" + this.squareSize + "px;width:" + this.squareSize + "px");

  row.append(square);

}


In the CSS, square is floated left and outline set to a solid red.
.row
{
  width: 100%;
  outline: 1px solid red;
}

.square
{
  float: left;
  outline: 1px solid red;
}


img
{
  display: none;
}


And here's a nice grid!



Next, we fill in those square divs using another div, styled with the CSS class square_contents.
for(var j = 0; j < 500 / this.squareSize; j++)
{
  var square = $("<div></div>");
  square.addClass("square");
  square.attr("style", "height:" + this.squareSize + "px;width:" + this.squareSize + "px");

  var content = $("<div></div>");
  content.addClass("square_contents");

  square.append(content);

  row.append(square);
}


Here's the CSS. It's mostly background-related CSS. You'll see why in a bit.
.square
{
  float: left;
  outline: 1px solid red;
}

.square_contents
{
  background-repeat: no-repeat;
  background-size: 500px 750px;
  margin: 0 auto 0 auto;
}


img
{
  display: none;
}


Back to the For loop... add in data-row and data-col values using i and j. Those will be useful later.
for(var j = 0; j < 500 / this.squareSize; j++)
{
  var square = $("<div></div>");
  square.addClass("square");
  square.attr("style", "height:" + this.squareSize + "px;width:" + this.squareSize + "px");

  var content = $("<div></div>");
  content.addClass("square_contents");
  content.attr("data-row", i);
  content.attr("data-col", j);


  square.append(content);
  row.append(square);
}


And then load the current image (represented by imgURL) in the background-image property. Use background-position to ensure that the background of each square div's content is offset by the appropriate number of pixels, using the values of i, j and squareSize.
for(var j = 0; j < 500 / this.squareSize; j++)
{
  var square = $("<div></div>");
  square.addClass("square");
  square.attr("style", "height:" + this.squareSize + "px;width:" + this.squareSize + "px");

  var content = $("<div></div>");
  content.addClass("square_contents");
  content.attr("data-row", i);
  content.attr("data-col", j);
  content.attr("style", "background-image:url(" + imgUrl + ");background-position:-" + (j * this.squareSize) + "px -" + (i * this.squareSize) + "px;opacity:1;width:" +  this.squareSize + "px;height:" + this.squareSize + "px");

  square.append(content);
  row.append(square);
}


And you see the image! Each square has its image offset, but put it all together, it still forms a coherent picture!



Now, let's remove the red lines. We won't be needing them.
.row
{
  width: 100%;
  outline: 0px solid red;
}

.square
{
  float: left;
  outline: 0px solid red;
}


After that, run the startTransition() method. Pass in the maxTransitionTime property as an argument.
fillContainer: function()
{
  var imgUrl = this.getRandomImage();

  for(var i = 0; i < 750 / this.squareSize; i++)
  {
  var row = $("<div></div>");
  row.addClass("row");
  row.attr("style", "height:" + this.squareSize + "px");

  for(var j = 0; j < 500 / this.squareSize; j++)
  {
    var square = $("<div></div>");
    square.addClass("square");
    square.attr("style", "height:" + this.squareSize + "px;width:" + this.squareSize + "px");

    var content = $("<div></div>");
    content.addClass("square_contents");
    content.attr("data-row", i);
    content.attr("data-col", j);
    content.attr("style", "background-image:url(" + imgUrl + ");background-position:-" + (j * this.squareSize) + "px -" + (i * this.squareSize) + "px;opacity:1;width:" +  this.squareSize + "px;height:" + this.squareSize + "px");

    square.append(content);
    row.append(square);
  }

  $("#container").append(row);
  }

  this.startTransition(this.maxTransitionTime);
}


We then create startTransition(). It has ms as a parameter. In it, we run the transition() method, passing in a tenth of ms and the squareSize property as arguments.
    
fillContainer: function()
{
  var imgUrl = this.getRandomImage();

  for(var i = 0; i < 750 / this.squareSize; i++)
  {
  var row = $("<div></div>");
  row.addClass("row");
  row.attr("style", "height:" + this.squareSize + "px");

  for(var j = 0; j < 500 / this.squareSize; j++)
  {
    var square = $("<div></div>");
    square.addClass("square");
    square.attr("style", "height:" + this.squareSize + "px;width:" + this.squareSize + "px");

    var content = $("<div></div>");
    content.addClass("square_contents");
    content.attr("data-row", i);
    content.attr("data-col", j);
    content.attr("style", "background-image:url(" + imgUrl + ");background-position:-" + (j * this.squareSize) + "px -" + (i * this.squareSize) + "px;opacity:1;width:" +  this.squareSize + "px;height:" + this.squareSize + "px");

    square.append(content);
    row.append(square);
  }

  $("#container").append(row);
  }

  this.startTransition(this.maxTransitionTime);
},
startTransition: function(ms)
{
  this.transition(ms / 10, this.squareSize);
}


Then create transition(). It will have ms and squareSize as parameters.
startTransition: function(ms)
{
  this.transition(ms / 10, this.squareSize);
},
transition: function(ms, squareSize)
{

}


We should stop here for now. It's about to get fun, but also a little complicated.

Next

Animating the display.

No comments:

Post a Comment