| OLD | NEW |
| (Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 (function(scope) { |
| 5 "use strict"; |
| 6 |
| 7 var width = 55; |
| 8 var scaleFactor = 200.0; |
| 9 var jumpHeight = 50; |
| 10 var scrollTops = []; |
| 11 var kernel = [0.1, 0.1, 0.1, 0.1, 0.2, 0.2, 0.2]; |
| 12 var epsilon = 0.0001; |
| 13 |
| 14 function createUIWorker() { |
| 15 scope.worker = new UIWorker('resources/snap-points.js'); |
| 16 |
| 17 var scroller = document.getElementById('scroller'); |
| 18 var dancer = document.getElementById('dancer'); |
| 19 var shadow = document.getElementById('shadow'); |
| 20 |
| 21 scope.worker.postMessage({ |
| 22 'cmd': 'setup-scene', |
| 23 'tokens': [ |
| 24 scroller.bindAnimatedProperty('scrollTop'), |
| 25 dancer.bindAnimatedProperty('transform'), |
| 26 shadow.bindAnimatedProperty('transform') |
| 27 ] |
| 28 }); |
| 29 |
| 30 document.body.addEventListener('touchstart', function(e) { |
| 31 scope.worker.postMessage({'cmd': 'touchstart'}); |
| 32 }); |
| 33 |
| 34 document.body.addEventListener('touchend', function(e) { |
| 35 scope.worker.postMessage({'cmd': 'touchend'}); |
| 36 }); |
| 37 } |
| 38 |
| 39 function translate(x, y) { |
| 40 return 'translate(' + x + 'px, ' + y + 'px) '; |
| 41 } |
| 42 |
| 43 function translate3d(x, y, z) { |
| 44 return 'translate3d(' + x + 'px, ' + y + 'px, ' + z + 'px) '; |
| 45 } |
| 46 |
| 47 function rotateX(deg) { |
| 48 return 'rotateX(' + deg + 'deg) '; |
| 49 } |
| 50 |
| 51 function rotateY(deg) { |
| 52 return 'rotateY(' + deg + 'deg) '; |
| 53 } |
| 54 |
| 55 function aboutPoint(x, y, transform) { |
| 56 return translate(x, y) + transform + translate(-x, -y); |
| 57 } |
| 58 |
| 59 function perspective(depth, x, y) { |
| 60 return aboutPoint(x, y, 'perspective(' + depth + 'px) '); |
| 61 } |
| 62 |
| 63 function matrixFromScrollTop(scrollTop, perspX, perspY) { |
| 64 var str = perspective(depth, x, y) + |
| 65 aboutPoint(0, width, rotateX(-scrollTop)); |
| 66 console.log(str); |
| 67 return new WebKitCSSMatrix(str); |
| 68 } |
| 69 |
| 70 function getTranslationForMove(index) { |
| 71 var x = 0; |
| 72 var y = 0; |
| 73 for (var i = 0; i < index; i++) { |
| 74 switch (moves[i]) { |
| 75 case 'up': y -= 2*width; break; |
| 76 case 'down': y += 2*width; break; |
| 77 case 'left': x -= 2*width; break; |
| 78 case 'right': x += 2*width; break; |
| 79 case 'jump-up': y -= 4*width; break; |
| 80 case 'jump-down': y += 4*width; break; |
| 81 case 'jump-left': x -= 4*width; break; |
| 82 case 'jump-right': x += 4*width; break; |
| 83 } |
| 84 } |
| 85 return translate(x, y); |
| 86 } |
| 87 |
| 88 function getRotationForMove(index, fraction) { |
| 89 var degrees = fraction * 180.0 + 0.0001; |
| 90 var radians = fraction * Math.PI; |
| 91 switch (moves[index]) { |
| 92 case 'up': return aboutPoint(0, -width, rotateX(degrees)); |
| 93 case 'down': return aboutPoint(0, width, rotateX(-degrees)); |
| 94 case 'left': return aboutPoint(-width, 0, rotateY(-degrees)); |
| 95 case 'right': return aboutPoint(width, 0, rotateY(degrees)); |
| 96 case 'jump-up': |
| 97 return translate3d(0, -4 * width * fraction, |
| 98 (-1.0 + Math.cos(radians * 2)) * -jumpHeight); |
| 99 case 'jump-down': |
| 100 return translate3d(0, 6 * width * fraction, |
| 101 (-1.0 + Math.cos(radians * 2)) * -0.2 * jumpHeigh
t) |
| 102 + aboutPoint(0, -width, rotateX(degrees)); |
| 103 case 'jump-left': |
| 104 return translate3d(-4 * width * fraction, 0, |
| 105 (-1.0 + Math.cos(radians * 2)) * -jumpHeight) |
| 106 + 'rotateZ(' + degrees + 'deg)'; |
| 107 case 'jump-right': |
| 108 return translate3d(4 * width * fraction, 0, |
| 109 (-1.0 + Math.cos(radians * 2)) * -jumpHeight); |
| 110 } |
| 111 } |
| 112 |
| 113 function tick(context) { |
| 114 var scrollTop = context.getScalar(scope.scroll); |
| 115 scrollTops.push(scrollTop); |
| 116 if (scrollTops.length == kernel.length + 3) { |
| 117 scrollTops.shift(); |
| 118 scrollTops.shift(); |
| 119 var sum = 0; |
| 120 for (var i = 0; i < kernel.length; ++i) { |
| 121 var ds = scrollTops[i + 1] - scrollTops[i]; |
| 122 sum += ds * kernel[i]; |
| 123 } |
| 124 var estimatedVelocity = (sum * 60.0) / 1000.0; |
| 125 if (!scope.lastVelocity) |
| 126 scope.lastVelocity = estimatedVelocity; |
| 127 scope.lastVelocity *= 0.2; |
| 128 scope.lastVelocity += 0.8 * estimatedVelocity; |
| 129 if (Math.abs(scope.lastVelocity) < epsilon) |
| 130 scope.lastVelocity = 0.0; |
| 131 } |
| 132 |
| 133 if (scope.inFling && |
| 134 !scope.gravityOn && |
| 135 Math.abs(scope.lastVelocity) < 0.05) { |
| 136 scope.gravityOn = true; |
| 137 scope.target = Math.round(scope.lastScrollTop / 200.0) * 200.0; |
| 138 } |
| 139 |
| 140 if (scope.gravityOn) { |
| 141 var correction = scope.target - scrollTop; |
| 142 scrollTop -= scope.lastVelocity; |
| 143 scope.lastVelocity = scope.lastVelocity * 0.97 + correction * 0.03; |
| 144 scrollTop += scope.lastVelocity; |
| 145 context.setScalar(scope.scroll, scrollTop); |
| 146 if (Math.abs(scrollTop - scope.target) < epsilon) { |
| 147 scope.inFling = false; |
| 148 scope.gravityOn = false; |
| 149 } |
| 150 } |
| 151 |
| 152 scope.lastScrollTop = scrollTop; |
| 153 |
| 154 var scaled = Math.floor(scrollTop / scaleFactor); |
| 155 var index = scaled % moves.length; |
| 156 var fraction = (scrollTop - scaled * scaleFactor) / scaleFactor; |
| 157 var translation = getTranslationForMove(index); |
| 158 var rotation = getRotationForMove(index, fraction); |
| 159 |
| 160 context.setMatrix(scope.dancer, |
| 161 new WebKitCSSMatrix(translation + dancerPerspective + rotation)); |
| 162 |
| 163 context.setMatrix(scope.shadow, |
| 164 new WebKitCSSMatrix(translation + shadowPerspective + rotation)); |
| 165 |
| 166 scope.teleportMessage(context, tick); |
| 167 } |
| 168 |
| 169 function setupScene(tokens) { |
| 170 scope.scroll = tokens[0]; |
| 171 scope.dancer = tokens[1]; |
| 172 scope.shadow = tokens[2]; |
| 173 |
| 174 // A cute trick: if you use different perspective origins, you get |
| 175 // directional shadows. |
| 176 scope.dancerPerspective = perspective(300, 50, 0); |
| 177 scope.shadowPerspective = perspective(300, 100, 40); |
| 178 |
| 179 // The following moves define the path taken by the dancer. |
| 180 scope.moves = [ |
| 181 "down", "right", "up", "right", "up", "left", "down", "left", |
| 182 "jump-right", "left", "down", "jump-up", "right", "down", "left", |
| 183 "jump-down", "up", "right", "up", "jump-left" |
| 184 ]; |
| 185 |
| 186 // TODO: we should have to request the current time. Otherwise, we should |
| 187 // wait until a change in the given values before spamming tick for max |
| 188 // battery efficiency. |
| 189 scope.teleportMessage(new TeleportContext(tokens), tick); |
| 190 } |
| 191 |
| 192 function onMessage(e) { |
| 193 switch (e.data.cmd) { |
| 194 case 'setup-scene': setupScene(e.data.tokens); break; |
| 195 case 'touchstart': |
| 196 scope.gravityOn = false; |
| 197 scope.inFling = false; |
| 198 break; |
| 199 case 'touchend': |
| 200 scope.inFling = true; |
| 201 scrollTops.length = 0; |
| 202 break; |
| 203 } |
| 204 } |
| 205 |
| 206 if (scope.window) { |
| 207 scope.window.onload = createUIWorker; |
| 208 } else { |
| 209 scope.onmessage = onMessage; |
| 210 } |
| 211 |
| 212 })(self); |
| OLD | NEW |