├── .gitignore ├── favicon.png ├── README.md ├── fish-pegged ├── page.js ├── index.html └── page.css ├── fish-swimming ├── index.html ├── page.css └── page.js ├── fish-swimming-more ├── index.html ├── page.css └── page.js ├── fish-pixels ├── page.css └── index.html ├── progress-bar ├── page.css └── index.html ├── LICENSE.md ├── button-color ├── index.html └── page.css ├── site.css └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | -------------------------------------------------------------------------------- /favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lonekorean/animation-workshop/HEAD/favicon.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # animation-workshop 2 | 3 | Various demos highlighting CSS animation performance. Check them out here: https://lonekorean.github.io/animation-workshop/. 4 | -------------------------------------------------------------------------------- /fish-pegged/page.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | $('button').on('click', doBusyWork); 3 | 4 | function doBusyWork() { 5 | var endTime = Date.now() + 10 * 1000; 6 | while (Date.now() < endTime) { 7 | // churn 8 | } 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /fish-swimming/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Fish Swimming 7 | 8 | 9 | 10 | 11 |
12 |

13 |
14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /fish-swimming-more/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Fish Swimming More 7 | 8 | 9 | 10 | 11 |
12 |

13 |
14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /fish-swimming-more/page.css: -------------------------------------------------------------------------------- 1 | body { 2 | overflow: hidden; 3 | } 4 | 5 | h1 { 6 | position: absolute; 7 | bottom: 10px; 8 | right: 10px; 9 | opacity: .1; 10 | margin: 0; 11 | font-size: 12rem; 12 | line-height: 1; 13 | } 14 | 15 | .bad-approach .fish { 16 | transition: top 5s, left 5s; 17 | } 18 | 19 | .bad-approach.will-change .fish { 20 | will-change: top, left; 21 | } 22 | 23 | .good-approach .fish { 24 | transition: transform 5s; 25 | } 26 | 27 | .good-approach.will-change .fish { 28 | will-change: transform; 29 | } 30 | 31 | .fish-1 { color: #ffd47b; } 32 | .fish-2 { color: #9bc95b; } 33 | .fish-3 { color: #95a9d6; } 34 | .fish-4 { color: #ffa8e1; } 35 | -------------------------------------------------------------------------------- /fish-pixels/page.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | margin: 40px 0 0; 3 | } 4 | 5 | h2 { 6 | margin: 0 0 80px; 7 | } 8 | 9 | .fish-anchor { 10 | display: inline-block; 11 | position: relative; 12 | } 13 | 14 | .fish { 15 | color: #ffd47b; 16 | animation-duration: 10s; 17 | animation-iteration-count: infinite; 18 | } 19 | 20 | .bad-fish { 21 | animation-name: bad-ani; 22 | } 23 | @keyframes bad-ani { 24 | 0%, 100% { 25 | left: -10px; 26 | } 27 | 50% { 28 | left: 10px; 29 | } 30 | } 31 | 32 | .good-fish { 33 | animation-name: good-ani; 34 | } 35 | @keyframes good-ani { 36 | 0%, 100% { 37 | transform: translateX(-10px); 38 | } 39 | 50% { 40 | transform: translateX(10px); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /fish-pixels/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Fish Pixels 7 | 8 | 9 | 10 | 11 |
12 |

bad fish

13 |

(animating left)

14 |
15 |
16 |
17 |
18 |
19 |

good fish

20 |

(animating transform)

21 |
22 |
23 |
24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /fish-pegged/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Fish Pegged 7 | 8 | 9 | 10 | 11 |
12 |

bad fish

13 |

(animating top + left)

14 |
15 |
16 |
17 |
18 |
19 |

good fish

20 |

(animating transform)

21 |
22 |
23 |
24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /fish-swimming/page.css: -------------------------------------------------------------------------------- 1 | body { 2 | overflow: hidden; 3 | } 4 | 5 | h1 { 6 | position: absolute; 7 | bottom: 10px; 8 | right: 10px; 9 | opacity: .1; 10 | margin: 0; 11 | font-size: 12rem; 12 | line-height: 1; 13 | } 14 | 15 | .fish-anchor { 16 | position: absolute; 17 | } 18 | 19 | .bad-approach .fish { 20 | animation: bad-swim 8s infinite; 21 | } 22 | @keyframes bad-swim { 23 | 0%, 100% { left: -200px; } 24 | 50% { left: 200px; } 25 | } 26 | 27 | .bad-approach.will-change .fish { 28 | will-change: top, left; 29 | } 30 | 31 | .good-approach .fish { 32 | animation: good-swim 8s infinite; 33 | } 34 | @keyframes good-swim { 35 | 0%, 100% { transform: translateX(-200px); } 36 | 50% { transform: translateX(200px); } 37 | } 38 | 39 | .good-approach.will-change .fish { 40 | will-change: transform; 41 | } 42 | 43 | .fish-1 { color: #ffd47b; } 44 | .fish-2 { color: #9bc95b; } 45 | .fish-3 { color: #95a9d6; } 46 | .fish-4 { color: #ffa8e1; } 47 | -------------------------------------------------------------------------------- /progress-bar/page.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: #222; 3 | background-color: #fff; 4 | } 5 | 6 | h1 { 7 | margin: 40px 0 0; 8 | } 9 | 10 | h2 { 11 | margin: 0 0 40px; 12 | } 13 | 14 | .anchor { 15 | display: inline-block; 16 | position: relative; 17 | } 18 | 19 | .progress-container { 20 | overflow: hidden; 21 | width: 430px; 22 | height: 20px; 23 | background-color: #ddd; 24 | } 25 | 26 | .progress { 27 | height: 20px; 28 | background-color: #95a9d6; 29 | animation-duration: 4s; 30 | animation-iteration-count: infinite; 31 | } 32 | 33 | .bad-progress { 34 | animation-name: bad-ani; 35 | } 36 | @keyframes bad-ani { 37 | 0% { 38 | width: 0%; 39 | } 40 | 100% { 41 | width: 100%; 42 | } 43 | } 44 | 45 | .good-progress { 46 | transform-origin: 0 0; 47 | animation-name: good-ani; 48 | } 49 | @keyframes good-ani { 50 | 0% { 51 | transform: scaleX(0); 52 | } 53 | 100% { 54 | transform: scaleX(100%); 55 | } 56 | } 57 | 58 | textarea { 59 | margin-top: 60px; 60 | width: 430px; 61 | height: 250px; 62 | padding: 20px; 63 | border: 2px dashed #999; 64 | font: 1.4rem/1.4 monospace; 65 | resize: none; 66 | } 67 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Will Boyd 2 | 3 | This software is released under the MIT license: http://opensource.org/licenses/MIT 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /progress-bar/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Progress Bar 7 | 8 | 9 | 10 | 11 |
12 |

less awesome

13 |

(animating width)

14 |
15 |
16 |
17 |
18 | 25 |
26 |
27 |
28 |

more awesome

29 |

(animating transform)

30 |
31 |
32 |
33 |
34 | 42 |
43 |
44 | 45 | 46 | -------------------------------------------------------------------------------- /button-color/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Button Color 7 | 8 | 9 | 10 | 11 |
12 |

less awesome

13 |

(animating background-color)

14 |
15 | 16 | 27 |
28 |
29 |
30 |

more awesome

31 |

(animating filter)

32 |
33 | 34 | 47 |
48 |
49 | 50 | 51 | -------------------------------------------------------------------------------- /button-color/page.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: #222; 3 | background-color: #fff; 4 | } 5 | 6 | h1 { 7 | margin: 40px 0 0; 8 | } 9 | 10 | h2 { 11 | margin: 0 0 40px; 12 | } 13 | 14 | .anchor { 15 | display: inline-block; 16 | position: relative; 17 | } 18 | 19 | button { 20 | display: block; 21 | margin: 0 auto; 22 | padding: 10px 60px; 23 | border: none; 24 | font-size: 2rem; 25 | text-transform: uppercase; 26 | letter-spacing: 0.1rem; 27 | color: #fff; 28 | cursor: pointer; 29 | -webkit-appearance: none; 30 | -moz-appearance: none; 31 | appearance: none; 32 | animation-duration: 2s; 33 | animation-iteration-count: infinite; 34 | } 35 | 36 | .bad-button { 37 | animation-name: bad-ani; 38 | } 39 | @keyframes bad-ani { 40 | 0%, 100% { 41 | background-color: #dbdbdb; 42 | } 43 | 80% { 44 | background-color: #ffa8e1; 45 | } 46 | } 47 | 48 | .good-button { 49 | background-color: #ffa8e1; 50 | animation-name: good-ani; 51 | } 52 | @keyframes good-ani { 53 | 0%, 100% { 54 | filter: grayscale(100%) brightness(115%); 55 | } 56 | 80% { 57 | filter: none; 58 | } 59 | } 60 | 61 | textarea { 62 | margin-top: 60px; 63 | width: 480px; 64 | height: 380px; 65 | padding: 20px; 66 | border: 2px dashed #999; 67 | font: 1.4rem/1.4 monospace; 68 | resize: none; 69 | } 70 | -------------------------------------------------------------------------------- /fish-pegged/page.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | margin: 40px 0 0; 3 | } 4 | 5 | h2 { 6 | margin: 0 0 180px; 7 | 8 | } 9 | 10 | .fish-anchor { 11 | display: inline-block; 12 | position: relative; 13 | } 14 | 15 | .fish { 16 | color: #ffd47b; 17 | animation-duration: 3s; 18 | animation-iteration-count: infinite; 19 | } 20 | 21 | .bad-fish { 22 | animation-name: bad-ani; 23 | } 24 | @keyframes bad-ani { 25 | 0%, 100% { 26 | top: 0; 27 | left: -100px; 28 | } 29 | 25% { 30 | top: -100px; 31 | left: 0; 32 | } 33 | 50% { 34 | top: 0; 35 | left: 100px; 36 | } 37 | 75% { 38 | top: 100px; 39 | left: 0; 40 | } 41 | } 42 | 43 | .good-fish { 44 | animation-name: good-ani; 45 | } 46 | @keyframes good-ani { 47 | 0%, 100% { 48 | transform: translate(-100px, 0); 49 | } 50 | 25% { 51 | transform: translate(0, -100px); 52 | } 53 | 50% { 54 | transform: translate(100px, 0); 55 | } 56 | 75% { 57 | transform: translate(0, 100px); 58 | } 59 | } 60 | 61 | button { 62 | position: absolute; 63 | top: 540px; 64 | left: calc(50%); 65 | transform: translateX(-50%); 66 | padding: 20px; 67 | border: 4px solid #fff;; 68 | border-radius: 10px; 69 | font-size: 2rem; 70 | color: #fff; 71 | background-color: #222; 72 | cursor: pointer; 73 | -webkit-appearance: none; 74 | -moz-appearance: none; 75 | appearance: none; 76 | } 77 | 78 | button:hover { 79 | background-color: #555; 80 | } 81 | -------------------------------------------------------------------------------- /site.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Open+Sans:400,700); 2 | 3 | *, *::before, *::after { 4 | box-sizing: border-box; 5 | } 6 | 7 | body { 8 | margin: 0; 9 | color: #fff; 10 | background-color: #1d3f59; 11 | font: 1.5rem/1.5 'Open Sans', sans-serif; 12 | } 13 | 14 | .prose { 15 | max-width: 900px; 16 | margin: 0 auto; 17 | padding: 40px; 18 | } 19 | 20 | h1 { 21 | margin: 0 0 40px; 22 | line-height: 1.2; 23 | font-size: 3.5rem; 24 | font-weight: normal; 25 | } 26 | 27 | h2 { 28 | font-size: 2rem; 29 | font-weight: normal; 30 | } 31 | 32 | a { 33 | color: #ffd47b; 34 | text-decoration: none; 35 | } 36 | 37 | a:hover { 38 | text-decoration: underline; 39 | } 40 | 41 | .pond { 42 | position: absolute; 43 | top: 0; 44 | right: 0; 45 | bottom: 0; 46 | left: 0; 47 | text-align: center; 48 | } 49 | 50 | .left-pond { 51 | left: 0; 52 | right: 50%; 53 | } 54 | 55 | .right-pond { 56 | right: 0; 57 | left: 50%; 58 | } 59 | 60 | .fish { 61 | position: absolute; 62 | width: 50px; 63 | height: 30px; 64 | border-radius: 50%; 65 | margin: -15px 0 0 -20px; 66 | background-color: currentColor; 67 | } 68 | 69 | /* tail */ 70 | .fish::before { 71 | content: ''; 72 | display: block; 73 | position: absolute; 74 | left: -10px; 75 | width: 0; 76 | height: 0; 77 | border-left: solid 25px currentColor; 78 | border-top: solid 15px transparent; 79 | border-bottom: solid 15px transparent; 80 | } 81 | 82 | /* eye */ 83 | .fish::after { 84 | content: ''; 85 | display: block; 86 | position: absolute; 87 | top: 8px; 88 | left: 34px; 89 | width: 6px; 90 | height: 6px; 91 | background-color: #fff; 92 | border-radius: 50%; 93 | } 94 | -------------------------------------------------------------------------------- /fish-swimming/page.js: -------------------------------------------------------------------------------- 1 | var $pond, pondWidth, pondHeight; 2 | 3 | $(function() { 4 | var $pond; 5 | init(); 6 | 7 | function init() { 8 | var approach = getParam('approach'); 9 | 10 | // setup pond 11 | $pond = $('.pond'); 12 | $pond.addClass(approach + '-approach'); 13 | if(getParam('will-change') === 'true') { 14 | $pond.addClass('will-change'); 15 | } 16 | 17 | // display count 18 | var count = getParam('count'); 19 | $('h1').text(count + ' fish'); 20 | 21 | // dump fish in 22 | spawnFish(count); 23 | 24 | $(window).on('resize', reload); 25 | } 26 | 27 | function reload() { 28 | window.location.reload(); 29 | } 30 | 31 | function getParam(key) { 32 | var matches = location.search.match(new RegExp(key + '=([^&]+)')); 33 | return matches ? matches[1] : undefined; 34 | } 35 | 36 | function spawnFish(count) { 37 | var pondWidth = $pond.width(); 38 | var pondHeight = $pond.height(); 39 | 40 | var $fishAnchors = []; 41 | for (var i = 0; i < count; i++) { 42 | // setup fish 43 | var $fish = $('
', { class: 'fish' }); 44 | var num = getRandom(4) + 1; 45 | $fish.addClass('fish-' + num); 46 | $fish.css('animation-delay', -getRandom(8000) + 'ms'); 47 | 48 | // position fish 49 | var $fishAnchor = $('
', { class: 'fish-anchor' }); 50 | $fishAnchor.css({ 51 | left: getRandom(pondWidth), 52 | top: getRandom(pondHeight) 53 | }); 54 | $fishAnchor.append($fish); 55 | $fishAnchors.push($fishAnchor); 56 | } 57 | 58 | // add them all at once to minimize DOM thrashing 59 | $pond.append($fishAnchors); 60 | } 61 | 62 | function getRandom(upper) { 63 | return Math.floor(Math.random() * upper); 64 | } 65 | }); 66 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Animation Workshop 8 | 9 | 10 | 11 | 12 | 13 |
14 |

Animation Workshop

15 |

16 | Demos highlighting different CSS animation techniques and how some perform better than others. 17 |

18 |

19 | Source code is available in the GitHub repo. 20 | Questions? Hit me up @lonekorean. 21 |

22 | 26 | 30 | 34 | 38 | 42 |
43 | 44 | 45 | -------------------------------------------------------------------------------- /fish-swimming-more/page.js: -------------------------------------------------------------------------------- 1 | var $pond, pondWidth, pondHeight; 2 | 3 | $(function() { 4 | var approach, isStaggered, $pond, pondWidth, pondHeight; 5 | init(); 6 | 7 | function init() { 8 | approach = getParam('approach'); 9 | isStaggered = getParam('stagger') === 'true'; 10 | 11 | // setup pond 12 | $pond = $('.pond'); 13 | $pond.addClass(approach + '-approach'); 14 | if(getParam('will-change') === 'true') { 15 | $pond.addClass('will-change'); 16 | } 17 | determinePondSize(); 18 | $(window).on('resize', determinePondSize); 19 | 20 | // display count 21 | var count = getParam('count'); 22 | $('h1').text(count + ' fish'); 23 | 24 | // dump fish in 25 | for (var i = 0; i < count; i++) { 26 | spawnFish(); 27 | } 28 | } 29 | 30 | function getParam(key) { 31 | var matches = location.search.match(new RegExp(key + '=([^&]+)')); 32 | return matches ? matches[1] : undefined; 33 | } 34 | 35 | function determinePondSize() { 36 | pondWidth = $pond.width(); 37 | pondHeight = $pond.height(); 38 | } 39 | 40 | function spawnFish() { 41 | // setup fish 42 | var $fish = $('
', { class: 'fish' }); 43 | var num = getRandom(4) + 1; 44 | $fish.addClass('fish-' + num); 45 | positionFish($fish, -40, pondHeight / 2); 46 | 47 | // let fish go 48 | $pond.append($fish); 49 | var delay = isStaggered ? getRandom(5000) : 0; 50 | setTimeout(moveFish.bind(this, $fish), delay); 51 | } 52 | 53 | function moveFish($fish) { 54 | positionFish($fish, getRandom(pondWidth), getRandom(pondHeight)); 55 | 56 | // let fish chill for 2 seconds for non-staggered demo 57 | var delay = isStaggered ? 5000 : 7000; 58 | setTimeout(moveFish.bind(this, $fish), delay); 59 | } 60 | 61 | function positionFish($fish, x, y) { 62 | switch (approach) { 63 | case 'bad': 64 | $fish.css({ 'left': x + 'px', 'top': y + 'px' }); 65 | break; 66 | case 'good': 67 | $fish.css('transform', 'translate(' + x + 'px, ' + y + 'px)'); 68 | break; 69 | } 70 | } 71 | 72 | function getRandom(upper) { 73 | return Math.floor(Math.random() * upper); 74 | } 75 | }); 76 | --------------------------------------------------------------------------------