CSS transitions create a gradual change from one visual state to another, which is just what you need to make Ottergram’s show/hide effect more polished.
When you create a CSS transition, you are telling the browser, “I would like this element’s styles to change to these new properties, and I would like for that change to take exactly as long as I tell you.”
One common example is the fly-out menu seen on many sites, such as the small-screen version of bignerdranch.com. In a browser with a narrow viewport, clicking the menu icon makes the navigation menu appear from the top – but it does not appear all at once. Instead, it slides down from the header, visually animating from the initial state (hidden) to the end state (visible) (Figure 7.11). Clicking the menu icon again causes the navigation menu to slide back up until it is hidden again.
Before you create the transition effect for showing and hiding the detail image, you will build a simpler transition for your thumbnails.
In general, you should create transitions in three steps:
Decide what the end state should be. One good approach is to add the CSS declarations for the end state to the target element. This allows you to see them in the browser and make sure that they look the way you intend.
Move the declarations from the target element’s existing declaration block to a new CSS declaration block. You may want to use a new class for the selector for the new block.
Add a
transition
declaration to the target element. The
transition
property tells the browser that it
will need to visually animate the changes from the current CSS
values to the end-state CSS values and that the transition
should take place over a specific period of time.
Your first transition will increase the
size of a thumbnail when you hover over it with the cursor (Figure 7.12).
However, you will not directly change the width
or height
styles. You will use the
transform property, which can alter the shape,
size, rotation, and location of an element without interrupting the flow of the elements around it.
The target element for this transition is the
.thumbnail-item
.
You will begin by adding a transform
declaration directly to the .thumbnail-item
element.
After you have tested it and determined that it is working the way you want, you will move the transformation to a new .thumbnail-item:hover
declaration block.
Finally, you will add a transition
declaration to .thumbnail-item
.
In styles.css, begin by adding a transform
declaration to .thumbnail-item
.
... .thumbnail-item { display: inline-block; min-width: 120px; max-width: 120px; border: 1px solid rgb(100%, 100%, 100%); border: 1px solid rgba(100%, 100%, 100%, 0.8); transform: scale(2.2); } ...
transform: scale(2.2)
tells the browser that
the element should be drawn at 220% of its original size. There
are many values that can be used with
transform
, including advanced 3D effects. The MDN has
good coverage of them at developer.mozilla.org/en-US/docs/Web/CSS/transform.
Save and view the changes in your browser (Figure 7.13).
You can see that the thumbnails are now much larger than before. In fact, they are too large. Change the value so that they are only a little bit larger:
... .thumbnail-item { display: inline-block; min-width: 120px; max-width: 120px; border: 1px solid rgb(100%, 100%, 100%); border: 1px solid rgba(100%, 100%, 100%, 0.8);transform: scale(2.2);transform: scale(1.2); } ...
After you save, you should see that the otter thumbnails are only slightly larger than their original size (Figure 7.14).
This scale for the thumbnails looks good, so you can move on to the next step.
Now it is time to move the end-state style to a new style
declaration and set up the transition for the
.thumbnail-item
element.
When the user hovers the mouse cursor over a thumbnail, that
thumbnail should increase its scale
by 120%.
Add a declaration block to styles.css that uses the modifier
:hover
to designate styles that
should only be applied when the user hovers over
the element.
... .thumbnail-item { display: inline-block; min-width: 120px; max-width: 120px; border: 1px solid rgb(100%, 100%, 100%); border: 1px solid rgba(100%, 100%, 100%, 0.8);transform: scale(1.2);} .thumbnail-item:hover { transform: scale(1.2); } ...
The proper name for this modifier is pseudo-class. The psuedo-class :hover
matches an element when the
user holds the mouse cursor over it. There are a number of
pseudo-class keywords that describe the various states an element
can be in. You will encounter some when you work
with forms later in this book, and you can search the MDN to learn more.
Next, make this change happen as a transition by adding a
transition
declaration to .thumbnail-item
in styles.css.
You need to specify the property to animate and how long the
animation should take.
... .thumbnail-item { display: inline-block; min-width: 120px; max-width: 120px; border: 1px solid rgb(100%, 100%, 100%); border: 1px solid rgba(100%, 100%, 100%, 0.8); transition: transform 133ms; } .thumbnail-item:hover { transform: scale(1.2); } ...
You set a transition
for the
transform
property. This tells the browser that
it will need to animate the change, but only for the
transform
property. You also specified that the
transition should take place over a period of 133 milliseconds.
Save and give your new transition a try. You should see that each thumbnail enlarges when you hover over it. When you move your mouse away, the transition runs in reverse, and the thumbnail returns to its original size (Figure 7.15).
The DevTools give you a handy way to test
pseudo-class states. Go to the elements panel and expand the tags
until one of the <li>
tags is displayed. Click the tag so
that it is highlighted and you will see an ellipsis to the left.
Click the ellipsis, and in the contextual menu that is revealed choose
:hover
from the list of pseudo-classes (Figure 7.16).
An orange
circle appears to the left of the <li>
tag in the elements
panel, telling you that one of the pseudo-classes has been
activated via the DevTools. The corresponding thumbnail will remain
in the :hover
state, even if you mouse over it
and then mouse away from it.
Open the contextual menu again, by clicking the orange circle, and disable the
:hover
state before you continue.
Your transition is nice, but
there is a small bug.
Currently, the hover effect causes parts of the thumbnail to be cut off.
This is because the transform
applied to the .thumbnail-item
does not cause its parent to adjust its size. The solution is to add a bit of padding
to the .thumbnail-list
. Change the vertical padding for .thumbnail-list
in styles.css.
... .thumbnail-list { flex: 0 1 auto; order: 2; display: flex; justify-content: space-between; list-style: none;padding: 0;padding: 20px 0; white-space: nowrap; overflow-x: auto; } ...
You used the shorthand for the padding
property.
The first value, 20px
, applies to the top and bottom padding, while the
second value applies to the left and right padding.
Make a similar adjustment inside your @media
query, but
add an extra padding of 35px
to the left and right.
... @media all and (min-width: 768px) { .main-content { ... } .thumbnail-list { flex-direction: column; order: 0; margin-left: 20px; padding: 0 35px; } ...
Save and check the results in your browser. This produces a nicer effect for the thumbnails (Figure 7.17).
Your hover effect is looking good! But it lacks that visual pop that would make it really special. With CSS transitions, you can not only specify how much time a transition should take, but also make it transition at different speeds during that time.
There are several timing functions that you can use with transitions. By default, the linear timing function is used, which makes the transition animate at a single, constant rate. The others are more interesting, and give the transition the feeling of speeding up or slowing down.
Update your transition in styles.css
so that it uses the ease-in-out
timing function.
This will make the rate of the transition slower at the beginning and end and faster in the middle.
... .thumbnail-item { display: inline-block; min-width: 120px; max-width: 120px; border: 1px solid rgb(100%, 100%, 100%); border: 1px solid rgba(100%, 100%, 100%, 0.8); transition: transform 133ms ease-in-out; } ...
Save and then hover over one of your thumbnails. The effect is subtle, but noticeable.
There are a number of timing functions available. See the list on the MDN at developer.mozilla.org/en-US/docs/Web/CSS/transition-timing-function.
Your transition
uses the same duration value and timing function for both
the transition to the end state and the transition from the end state.
That does not have to be the case – you can use different values depending on
the direction of the transition.
If you specify a transition
property on both
the beginning-state declaration and the end-state declaration,
the browser uses the value of
the declaration it is moving toward.
It might be easier to see this in action.
For a quick demonstration, add a transition
declaration to .thumbnail-item:hover
in styles.css.
(You will delete it after trying it out in the browser.)
... .thumbnail-item:hover { transform: scale(1.2); transition: transform 1000ms ease-in; } ...
Save and again hover over one of the thumbnails in the browser.
The scaling effect will be very slow, taking
a full second to complete. This is because it is
using the value declared for .thumbnail-item:hover
.
Now, move your mouse off of
the thumbnail. This time, the transition takes 133 milliseconds,
the value declared for .thumbnail-item
.
Remove the transition
declaration
from .thumbnail-item:hover
before you continue.
... .thumbnail-item:hover { transform: scale(1.2);transition: transform 1000ms ease-in;} ...
Your second transition will make
the .detail-image-frame
look like it is zooming in from very far away.
Instead of using a pseudo-class selector to trigger a transition, this time you will add and remove class names with JavaScript to trigger a transition. Why? Because there is no pseudo-class that corresponds to a click event. Using JavaScript gives you much more control over how and when these UI changes are triggered.
Also, you will set different duration times for the beginning and end of the transition. The end result will be that when you click a thumbnail the corresponding otter image will be used for the detail image. It will immediately be sized down to a tiny dot in the center of the detail area, then it will transition to its full size (Figure 7.18).
Start by adding a style declaration for a new class named
is-tiny
in styles.css.
... .detail-image-frame { ... } .is-tiny { transform: scale(0.001); transition: transform 0ms; } .detail-image { ...
You added two styles for .is-tiny
. The first
scale
s the element down to a small fraction of
its original size. The second specifies that any
transition
for the transform
property should last 0 milliseconds, applying the
style change immediately. Put another way, going toward the .is-tiny
class styles, the detail image will
effectively have no transition. Because it lasts for 0 milliseconds,
there is no need to specify a timing function.
Next, you will add another transition
declaration
with a 333 millisecond duration. This value will be used when transitioning
away from the .is-tiny
class, making the detail image
grow to normal size over a period of a third of a second.
Add this transition
declaration to the
.detail-image-frame
in styles.css.
... .detail-image-frame { position: relative; text-align: center; transition: transform 333ms; } ...
Save styles.css before you move on.
Now that your transition styles are in place, you need to trigger
them with JavaScript. To give your JavaScript a hook, add a data attribute to the
.detail-image-frame
element in index.html.
... <div class="detail-image-container"> <div class="detail-image-frame" data-image-role="frame"> <img class="detail-image" data-image-role="target" src="img/otter1.jpg" alt=""> <span class="detail-image-title" data-image-role="title">Stayin' Alive</span> </div> </div> ...
Save index.html.
Now, in main.js, you just need to add
variables for your .is-tiny
class and data-image-role="frame"
selector, and then you will update
showDetails to perform the class name
changes to that trigger the transition.
Begin with the variables.
Add a DETAIL_FRAME_SELECTOR
variable for a selector
string '[data-image-role="frame"]'
.
Also, add a TINY_EFFECT_CLASS
variable for
the is-tiny
class name.
var DETAIL_IMAGE_SELECTOR = '[data-image-role="target"]'; var DETAIL_TITLE_SELECTOR = '[data-image-role="title"]'; var DETAIL_FRAME_SELECTOR = '[data-image-role="frame"]'; var THUMBNAIL_LINK_SELECTOR = '[data-image-role="trigger"]'; var HIDDEN_DETAIL_CLASS = 'hidden-detail'; var TINY_EFFECT_CLASS = 'is-tiny'; var ESC_KEY = 27; ...
It is not required that you put your variables in this order. (It makes no difference to the browser.) But it is a good idea to keep them organized. In main.js, all of the selector variables are grouped together, followed by the class variables, followed by the numeric code for the Escape key.
Now, update showDetails in main.js so that it
gets a reference to the [data-image-role="frame"]
element.
To trigger the transition
, you will need to add the
TINY_EFFECT_CLASS
and remove it.
... function showDetails() { 'use strict'; var frame = document.querySelector(DETAIL_FRAME_SELECTOR); document.body.classList.remove(HIDDEN_DETAIL_CLASS); frame.classList.add(TINY_EFFECT_CLASS); frame.classList.remove(TINY_EFFECT_CLASS); } ...
If you saved this and tried it in the browser, you would not see a transition
take place.
Why not? Because the TINY_EFFECT_CLASS
is added and then
immediately removed. The net result is that there is no actual
class change to render. This is an optimization on the part of the
browser.
You need to add
a small delay before removing the TINY_EFFECT_CLASS
.
JavaScript, however, does not have a built-in delay or sleep function, as some
other languages do. Time for a workaround!
You are going to use the setTimeout method, which takes a function and a delay (specified in milliseconds). After the delay, the function is queued for execution by the browser.
Add a call to setTimeout after
calling frame.classList.add in main.js. Pass it two
arguments: a function with a list of steps to perform and the number
of milliseconds to wait before invoking that function argument.
There is only one step to perform, and that is to remove
TINY_EFFECT_CLASS
.
... function showDetails() { 'use strict'; var frame = document.querySelector(DETAIL_FRAME_SELECTOR); document.body.classList.remove(HIDDEN_DETAIL_CLASS); frame.classList.add(TINY_EFFECT_CLASS); setTimeout(function () { frame.classList.remove(TINY_EFFECT_CLASS); }, 50); } ...
Let’s take a closer look at what this code does.
First, it adds the .is-tiny
class to the
frame
element. This applies your
transform: scale(0.001)
.
Then, the browser is told to wait 50 milliseconds, after which it will add an anonymous function to its execution queue. The showDetails function finishes. Fifty milliseconds later, the anonymous function is queued for execution. (Basically, it gets in line for the CPU, waiting behind any other functions that were already in line.)
When this
anonymous function runs, it removes the
TINY_EFFECT_CLASS
from the frame
’s
class list. This causes the transform
transition to run
over a period of 333 milliseconds, making the frame
grow to
its normal size.
Save your changes and admire the results. Click the thumbnails and enjoy those wacky otters zooming into view.
13.59.197.213