Skip to content

Commit 6dc4c6d

Browse files
committed
step5 and final version
1 parent 63b5c94 commit 6dc4c6d

File tree

5 files changed

+117
-88
lines changed

5 files changed

+117
-88
lines changed

iconpopups/final/index.html

+2-82
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,15 @@
88
<script src="https://cdn.rawgit.com/web-animations/web-animations-js/2.1.2/web-animations-next.min.js"></script>
99
<script src="../shared/codelab.js"></script>
1010
<script src="site.js"></script>
11-
<style type="text/css">
12-
13-
.fill {
14-
background: #eee;
15-
position: absolute;
16-
z-index: -10;
17-
top: 0;
18-
border-radius: 100%;
19-
transform-origin: center top;
20-
}
21-
22-
</style>
2311
</head>
2412
<body>
2513

14+
<!-- Group template, used in groupClick -->
2615
<template id="icon-group">
2716
<div class="group">
2817
<div class="popup">
29-
<div class="fill"></div>
3018
<div class="icons"></div>
19+
<div class="fill"></div>
3120
</div>
3221
</div>
3322
</template>
@@ -36,74 +25,5 @@
3625
<!-- Default, random icons created by codelab.js -->
3726
</div>
3827

39-
<script>
40-
41-
window.addEventListener('load', function() {
42-
var groups = Array.prototype.slice.call(document.querySelectorAll('.group'));
43-
var activePlayer = null;
44-
var activeTarget = null;
45-
46-
function closeActive() {
47-
if (activePlayer && activePlayer.playbackRate > 0) {
48-
activePlayer.reverse();
49-
activePlayer = null;
50-
activeTarget = null;
51-
}
52-
}
53-
54-
groups.forEach(function(group) {
55-
group.addEventListener('click', function(event) {
56-
event.stopPropagation();
57-
if (activeTarget == group) {
58-
return; // don't animate again
59-
}
60-
closeActive();
61-
62-
var popup = group.querySelector('.popup');
63-
var icons = popup.querySelector('.icons');
64-
var fill = popup.querySelector('.fill');
65-
66-
var rect = popup.getBoundingClientRect();
67-
var dim = Math.max(rect.width, rect.height);
68-
var longEdge = Math.sqrt(rect.width*rect.width + rect.height*rect.height);
69-
if (!longEdge) {
70-
return; // no icons
71-
}
72-
fill.style.width = longEdge + 'px';
73-
fill.style.height = longEdge + 'px';
74-
fill.style.top = -((longEdge - dim)/2) + 'px'
75-
fill.style.left = -((longEdge - rect.width)/2) + 'px';
76-
77-
var duration = 1/Math.atan(rect.height/100) * rect.height * 2;
78-
var timing = {
79-
duration: duration,
80-
easing: 'ease-in-out',
81-
fill: 'forwards',
82-
};
83-
84-
var fillAnim = new KeyframeEffect(fill, [
85-
{transform: 'scale(0)'},
86-
{transform: 'scale(1)'},
87-
], timing);
88-
89-
var iconsAnim = new KeyframeEffect(icons, [
90-
{overflow: 'hidden', 'max-height': 0, opacity: .8},
91-
{overflow: 'hidden', 'max-height': rect.height + 'px', opacity: 1},
92-
], timing);
93-
94-
var popupFrame = {visibility: 'visible', height: rect.height + 'px'};
95-
var popupAnim = new KeyframeEffect(popup, [popupFrame, popupFrame], timing);
96-
97-
var anim = new GroupEffect([fillAnim, iconsAnim, popupAnim]);
98-
activePlayer = document.timeline.play(anim);
99-
activeTarget = group;
100-
});
101-
});
102-
103-
document.body.addEventListener('click', closeActive);
104-
});
105-
106-
</script>
107-
10828
</body>
10929
</html>

iconpopups/final/site.css

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11

2-
/*.group:hover .popup {
3-
visibility: visible;
2+
.fill {
43
background: #eee;
4+
position: absolute;
5+
z-index: -10;
6+
top: 0;
7+
border-radius: 100%;
8+
transform-origin: center top;
59
}
6-
*/

iconpopups/final/site.js

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
2+
var activeTarget = null;
3+
var activePlayer = null;
4+
5+
/**
6+
* Close any active group target.
7+
*/
8+
function closeActive() {
9+
if (!activeTarget) { return; }
10+
11+
var popup = activeTarget.querySelector('.popup');
12+
activePlayer.reverse();
13+
14+
activeTarget = null;
15+
activePlayer = null;
16+
}
17+
18+
/**
19+
* Handle a click on a group. Responsible for closing any previous groups.
20+
* @param {!Element} group selected, as per "icon-group" template
21+
*/
22+
function groupClick(group) {
23+
if (activeTarget) {
24+
if (activeTarget == group) {
25+
return; // already visible, do nothing
26+
}
27+
closeActive();
28+
}
29+
activeTarget = group;
30+
31+
// Change the visibility of the group's popup.
32+
var popup = activeTarget.querySelector('.popup');
33+
//popup.style.visibility = 'visible';
34+
var frame = {'visibility': 'visible'};
35+
var popupEffect = new KeyframeEffect(popup, [frame, frame], {fill: 'forwards'});
36+
37+
// Find the 'longEdge', the length of the diagonal in the popup's rect.
38+
var rect = popup.getBoundingClientRect();
39+
var dim = Math.max(rect.width, rect.height);
40+
var longEdge = Math.sqrt(rect.width*rect.width + rect.height*rect.height);
41+
42+
// Update the fill object with the longEdge's size.
43+
var fill = popup.querySelector('.fill');
44+
fill.style.width = longEdge + 'px';
45+
fill.style.height = longEdge + 'px';
46+
fill.style.top = -((longEdge - dim)/2) + 'px'
47+
fill.style.left = -((longEdge - rect.width)/2) + 'px';
48+
49+
// Perform a simple animation: scale(0) => scale(1).
50+
var timing = {
51+
duration: rect.height * 2,
52+
easing: 'ease-out',
53+
};
54+
var fillEffect = new KeyframeEffect(fill, [{transform: 'scale(0)'}, {transform: 'scale(1)'}], timing);
55+
56+
// Create icon appear effects.
57+
var icons = Array.prototype.slice.call(popup.querySelectorAll('.ball'));
58+
var iconEffect = new SequenceEffect(icons.map(function(icon) {
59+
var effect = [{opacity: 0}, {opacity: 1}];
60+
var timing = {duration: rect.height * 2 / icons.length, fill: 'backwards'};
61+
return new KeyframeEffect(icon, effect, timing);
62+
}));
63+
64+
var groupEffect = new GroupEffect([fillEffect, popupEffect, iconEffect], {fill: 'forwards'});
65+
activePlayer = document.timeline.play(groupEffect);
66+
}

iconpopups/step4.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ This might seem verbose, but it showcases a hugely important part of Web Animati
2222

2323
Rather than setting the CSS `visibility` property directly, let's add another effect that we can run as part of the group.
2424

25-
Remove all the lines in the file which modify this property. First, inside `closeActive` (leave the event listener for later though, as we'll still need it)-
25+
Remove all the lines in the file which modify this property. First, inside `closeActive`, either comment the visibility line, or remove the whole listener-
2626

2727
```js
2828
activePlayer.addEventListener('finish', function() {

iconpopups/step5.md

+42-2
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,49 @@
22

33
The effect we've generated looks cool, but it has a clear issue - the icons being displayed are immediately visible, while the effect has a short duration.
44

5-
Instead of showing each icon immediately, let's fade them in over time. The top icon can fade immediately, followed by the next icon, and so on. We can achieve this using a `SequenceEffect` (for each icon), and a `GroupEffect` (to combine with the reveal effect). The layout of our animations will look a bit like this-
5+
Instead of showing each icon immediately, let's fade them in over time. The top icon can fade immediately, followed by the next icon, and so on. We can achieve this using a `SequenceEffect` (so each icon reveals after each other), and our existing `GroupEffect`. The layout of our animations will look a bit like this-
66

77
![Groups](resources/step5-groups.png)
88

9-
Let's get started!
9+
Since we've already done some groundwork for our groups and player, this step will be pretty simple. Let's get started!
1010

11+
### Icon Effects
12+
13+
Let's create an array of effects for each one of our icons. Above our current `GroupEffect` code in `site.js`, add these lines to create a single `SequenceEffect`-
14+
15+
```js
16+
var icons = Array.prototype.slice.call(popup.querySelectorAll('.ball'));
17+
var iconEffect = new SequenceEffect(icons.map(function(icon) {
18+
var effect = [{opacity: 0}, {opacity: 1}];
19+
var timing = {duration: rect.height * 2 / icons.length, fill: 'backwards'};
20+
return new KeyframeEffect(icon, effect, timing);
21+
}));
22+
```
23+
24+
Each animation is basically the same, with a duration relative to the total duration of the outer animation (which was `rect.height * 2`).
25+
26+
Note that we set these animations to `fill: 'backwards'`, which means that *before* they start playing, their value will be set. This is important as each icon fades in one at a time, and they should be transparent before that happens.
27+
28+
### Combining it all together
29+
30+
Next, add the effect to the `GroupEffect` constructor-
31+
32+
```js
33+
var groupEffect = new GroupEffect([fillEffect, popupEffect, iconEffect]);
34+
```
35+
36+
And we're almost done. The next change is very important, but a complex concept to explain. Because we have animations that `fill: 'backwards'`, and our animation is transient and finishes at the *start* of the animation, we should make sure the effects we specify don't apply forever.
37+
38+
This is simple to avoid. Let's modify the same line to specify that the group should only `fill: 'forwards'` (by default, groups or sequences fill in both directions), so that when our popup is open, all effects are still applied-
39+
40+
```js
41+
var groupEffect = new GroupEffect([fillEffect, popupEffect, iconEffect], {fill: 'forwards'});
42+
```
43+
44+
Without this change, the icons would be permanently held at `opacity: 0`. This would be the case even as combined with further openings of the popup (aka, future animations). This leaks memory and causes the animations to interact badly together.
45+
46+
## Done
47+
48+
You're all done! The final version of the site is located in `iconpopups/final`.
49+
50+
**Our conclusion is in [Step 6: Congratulations! &raquo](step6.md)**

0 commit comments

Comments
 (0)