7
Visual Effects with CSS

In the last chapter, you gave Ottergram the ability to respond to user interaction by changing the detail image when the user clicks a thumbnail. You will build on that in this chapter by adding three different visual effects to Ottergram.

The first effect is a simple layout change that involves hiding the detail image and letting the thumbnails take up the width of the page. When the user clicks a thumbnail, you will make the detail image reappear and return the thumbnails to their previous size.

The other two effects will use CSS to create visual animations for the thumbnails and the detail image (Figure 7.1).

Figure 7.1  Ottergram with transition effects

Ottergram with transition effects

Hiding and Showing the Detail Image

Ottergram’s users may want to be able to scroll through the thumbnails without the detail image being on the page (Figure 7.2).

Figure 7.2  Detail image visible and hidden

Detail image visible and hidden

To make this happen, you need to be able to apply styles to your .thumbnail-list and .detail-image-container based on a condition that will turn on and off as the website is in use. You could do this by creating new class selectors, like .thumbnail-list-no-detail and .hidden-detail-image-container, and add those classes to the target elements with JavaScript. The trouble with this approach is that it would be inefficient.

The event that will cause the detail image to hide will simultaneously cause the thumbnail list to reposition itself. It is a single event. Adding classes to your <ul> and <div> elements separately does not reflect this.

A better approach is to use JavaScript to add a single class selector that affects the layout as a whole. Then, you can target the .thumbnail-list and .detail-image-container when they are descendents of the new selector.

You are going to dynamically add a class name to the <body> element to hide the detail image and enlarge the thumbnails, and then dynamically remove the class name to return to your current styles (Figure 7.3).

Figure 7.3  Restyling descendants with class change to ancestor

Restyling descendants with class change to ancestor

This technique is similar in two ways to the one you used with the media queries.

First, it involves styles that are activated when an ancestor meets a particular condition. With media queries, that ancestor is the viewport and the condition is a minimum width. In this new code, the ancestor will be any element you select that the target elements share, and the condition will be that the ancestor has a particular class name.

The second similarity is that you must place the conditional styles after the other declarations for the affected elements in your stylesheet because these conditional styles need to override the previous declarations when they are active.

You will proceed in three steps:

  1. In your CSS, define the styles that create the visual effect you are trying to get. Also, test your styles in the DevTools.

  2. Write JavaScript functions to add and remove a class name for the <body> element.

  3. Add an event listener to trigger your JavaScript function.

Creating styles to hide the detail image

To hide the .detail-image-container, you will add a declaration that sets display: none for this element. display: none tells the browser that the element should not be rendered.

The class you will be adding dynamically to the <body> will be called hidden-detail. Therefore, you only want to apply display: none to .detail-image-container when it is a descendent of .hidden-detail.

Add the style to hide the detail image in styles.css:

...
.detail-image-title {
  ...
}

.hidden-detail .detail-image-container {
  display: none;
}

@media all and (min-width: 768px) {
  ...
}

Now, give some thought to what your .thumbnail-list will look like. Based on the current styles, it will be a column along the left side of wider screens and a horizontal row at the top of narrower screens. A centered column would be better when the detail image is hidden, regardless of the screen size.

Add styles to the .thumbnail-list and .thumbnail-item in styles.css when they are descendents of .hidden-detail.

...
.hidden-detail .detail-image-container {
  display: none;
}

.hidden-detail .thumbnail-list {
  flex-direction: column;
  align-items: center;
}

.hidden-detail .thumbnail-item {
  max-width: 80%;
}

@media all and (min-width: 768px) {
  ...
}

Now, the .thumbnail-list will always be displayed as a column while the .detail-image-container is hidden.

You have also added a declaration setting the width of the .thumbnail-item elements to max-width: 80% when the detail image is hidden. This overrides the max-width styles set elsewhere for the .thumbnail-items so that they will become the focus of the page.

When the .detail-image-container, .thumbnail-list, and .thumbnail-item elements are nested anywhere inside of an element with the class hidden-detail, these new styles will be activated.

Note that you added these before your media queries. As you already know, the order of your CSS code matters, with styles that appear later in the file overriding those that came before. In general, for the same selector, the browser uses the styles it has seen most recently. In this case, however, your new styles use selectors that are more specific than the ones that appear in your media queries, and specificity trumps recency.

Generally, it is best to keep your media queries at the end of the file. Your media queries will usually reuse the same selectors from existing styles, so putting them at the end makes sure that your media queries overwrite those existing styles. Also, it makes your media queries easier to locate, because they are always at the end of the file.

Save your file. Before you write the JavaScript that depends on the styles you have added, it is wise to test them. Start browser-sync (using browser-sync start --server --browser "Google Chrome" --files "*.html, stylesheets/*.css, scripts/*.js") and open the DevTools. In the elements panel, Control-click (right-click) the <body> element and choose Add Attribute from the menu that appears (Figure 7.4).

Figure 7.4  Choosing the Add Attribute menu item

Choosing the Add Attribute menu item

The DevTools provides a space for you to start typing inside the <body> tag. Enter class="hidden-detail" and press Return (Figure 7.5).

Figure 7.5  Adding the hidden-detail class attribute

Adding the hidden-detail class attribute

After you add the hidden-detail class to the <body> in the DevTools, the detail image disappears and the thumbnails become much larger – just as you intended (Figure 7.6).

Figure 7.6  Layout change after applying hidden-detail class

Layout change after applying hidden-detail class

Writing the JavaScript to hide the detail image

Next, you will write the JavaScript that will toggle the .hidden-detail class for the <body> element.

In main.js, add a variable named HIDDEN_DETAIL_CLASS.

var DETAIL_IMAGE_SELECTOR = '[data-image-role="target"]';
var DETAIL_TITLE_SELECTOR = '[data-image-role="title"]';
var THUMBNAIL_LINK_SELECTOR = '[data-image-role="trigger"]';
var HIDDEN_DETAIL_CLASS = 'hidden-detail';
...

Now, write a new function in main.js named hideDetails. Its job is to add a class name to the <body> element. You will use the classList.add DOM method to manipulate the class name.

...
function getThumbnailsArray() {
  ...
}

function hideDetails() {
  'use strict';
  document.body.classList.add(HIDDEN_DETAIL_CLASS);
}

function initializeEvents() {
  ...
}
...

You accessed the <body> element using the document.body property. This DOM element corresponds to the <body> tag in your markup. Like all DOM elements, it gives you a convenient way to manipulate its class names.

You also called the add method on document.body to add the hidden-detail class to the <body>.

Listening for the keypress event

Now you need a way to trigger the detail image to hide. As before, you will use an event listener, but this time your event listener will listen for a keypress instead of a click.

We use the term “keypress” generally to mean pressing and releasing a key, but that simple process actually triggers multiple events. When the key is first depressed, the keydown event is sent. If it is a character key (as opposed to a modifier key like Shift) then the keypress event is also sent. When the key is released, the keyup event is sent.

For Ottergram, these differences are minimal. You are going to use the keyup event.

In main.js, add a function named addKeyPressHandler that calls document.body.addEventListener, passing it the string 'keyup' and an anonymous function that declares a parameter named event. Inside the body of this anonymous function, make sure to preventDefault for the event, and then console.log the event’s keyCode.

...
function hideDetails() {
  ...
}

function addKeyPressHandler() {
  'use strict';
  document.body.addEventListener('keyup', function (event) {
    event.preventDefault();
    console.log(event.keyCode);
  });
}

function initializeEvents() {
  ...
}
...

All of the keypress events have a property called keyCode that corresponds to the key that triggered the event. The keyCode is an integer, like 13 for Return, 32 for the space bar, and 38 for the up arrow.

Update the initializeEvents function in main.js so that it calls addKeyPressHandler. You need to do this so that the <body> element can listen for keyboard events when the page loads.

...
function initializeEvents() {
  'use strict';
  var thumbnails = getThumbnailsArray();
  thumbnails.forEach(addThumbClickHandler);
  addKeyPressHandler();
}

initializeEvents();

Save and switch back to the browser. Make sure the console is visible, then click on the page to make sure that the focus is not on the DevTools – otherwise, the event listener will not be triggered. Now press some keys on your keyboard. You will see numbers printed to the console (Figure 7.7).

Figure 7.7  Logging the keyCode to the console

Logging the keyCode to the console

You want to hide the detail image when the Esc key is pressed, not just any key. If you press the Esc key, you will see that the corresponding event.keyCode value is 27. You will use that to make your event listener more specific.

Add a variable to the top of main.js for the Esc key’s value.

var DETAIL_IMAGE_SELECTOR = '[data-image-role="target"]';
var DETAIL_TITLE_SELECTOR = '[data-image-role="title"]';
var THUMBNAIL_LINK_SELECTOR = '[data-image-role="trigger"]';
var HIDDEN_DETAIL_CLASS = 'hidden-detail';
var ESC_KEY = 27;
...

Now, update your keyup event listener to call hideDetails when the value of event.keyCode matches the value of ESC_KEY.

...
function addKeyPressHandler() {
  'use strict';
  document.body.addEventListener('keyup', function (event) {
    event.preventDefault();
    console.log(event.keyCode);
    if (event.keyCode === ESC_KEY) {
      hideDetails();
    }
  });
}
...

You used the strict equality operator (===) to compare the values of event.keyCode and ESC_KEY. When these value are the same, you call hideDetails.

You could have used the loose equality operator (==) to compare the values instead, but it is usually best to use the strict equality operator. The major difference between the equality operators is that the loose equality operator will automatically convert from one type of value to another. The strict equality operator will not do the conversion. With strict equality, if the types are not the same, then the result of the comparison is always false.

Many front-end developers refer to this automatic type conversion as type coercion. It is performed when values need to be compared (when using an equality operator), added together (in the case of numbers), or concatenated (as with strings).

Because of this automatic conversion, there is no syntax error if you try to add the string "27" with the number 42 – though the result might not be what you expect (Figure 7.8).

Figure 7.8  JavaScript will automatically convert between types

JavaScript will automatically convert between types

This is very important when working with user-provided data, which you will do in Chapter 10.

Save main.js and test your new functionality in the browser (Figure 7.9).

Figure 7.9  Poof! Pressing Esc hides the detail image and title

Poof! Pressing Esc hides the detail image and title

Showing the detail image again

There is one small but important piece to add: making the detail image visible again. This will be triggered when a thumbnail is clicked.

You used classList.add to add a class name to the <body> element. You will use classList.remove to remove that class name when a thumbnail is clicked. Add a new function named showDetails to main.js.

...
function hideDetails() {
  ...
}

function showDetails() {
  'use strict';
  document.body.classList.remove(HIDDEN_DETAIL_CLASS);
}

function addKeyPressHandler() {
  ...
}
...

Now add a call to showDetails in your addThumbClickHandler function – no need to add a new event listener.

...
function addThumbClickHandler(thumb) {
  'use strict';
  thumb.addEventListener('click', function (event) {
    event.preventDefault();
    setDetailsFromThumb(thumb);
    showDetails();
  });
}
...

Save main.js and switch to your browser. Try out your new functionality: Hide the detail image, then click on a thumbnail to bring it back (Figure 7.10). The otters look like they approve, don’t they?

Figure 7.10  Esc hides details; click shows details

Esc hides details; click shows details

Now, Ottergram can dynamically adapt its layout based on the viewport, using media queries, as well as in response to user input.

At the moment, the layout changes happen abruptly. In the next section, you will smooth that out using CSS transitions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.131.212