transform-interpolation-001.html (11477B)
1 <!DOCTYPE html> 2 <meta charset="UTF-8"> 3 <title>transform interpolation</title> 4 <link rel="help" href="https://drafts.csswg.org/css-transforms/#transform-property"> 5 <meta name="assert" content="transform supports animation as a transform list"> 6 <meta name="timeout" content="long"> 7 8 <script src="/resources/testharness.js"></script> 9 <script src="/resources/testharnessreport.js"></script> 10 <script src="/css/support/interpolation-testcommon.js"></script> 11 12 <style> 13 .target { 14 color: white; 15 width: 100px; 16 height: 100px; 17 background-color: black; 18 display: inline-block; 19 overflow: hidden; 20 } 21 .expected { 22 background-color: green; 23 } 24 .target > div { 25 width: 10px; 26 height: 10px; 27 display: inline-block; 28 background: orange; 29 margin: 1px; 30 } 31 .test { 32 overflow: hidden; 33 } 34 </style> 35 36 <body> 37 <template id="target-template"> 38 <div></div> 39 </template> 40 </body> 41 42 <script> 43 44 // The default comparison function calls normalizeValue, which rounds 45 // everything to two decimal places, which isn't OK for the matrices 46 // that result from large perspective values. 47 const compareWithPerspective = (actual, expected) => { 48 // TODO: This RegExp should be more precise to capture only what is a 49 // valid float, and this code should be merged with other code doing 50 // the same thing, e.g., RoundMatrix in 51 // web-animations/animation-model/animation-types/property-list.js . 52 const matrixRegExp = /^matrix3d\(((?:(?:[-0-9.e]+), ){15}(?:[-0-9.]+))\)$/; 53 const actualMatch = actual.match(matrixRegExp); 54 const expectedMatch = expected.match(matrixRegExp); 55 assert_not_equals(actualMatch, null, `${actual} should be a matrix`); 56 assert_not_equals(expectedMatch, null, `${expected} should be a matrix`); 57 if (actualMatch === null || expectedMatch === null) { 58 return; 59 } 60 const actualArray = actualMatch[1].split(", ").map(Number); 61 const expectedArray = expectedMatch[1].split(", ").map(Number); 62 assert_equals(actualArray.length, 16); 63 assert_equals(expectedArray.length, 16); 64 65 if (actualArray.length != expectedArray.length) { 66 return; 67 } 68 69 for (let i in actualArray) { 70 const error = Math.abs((actualArray[i] - expectedArray[i])) / 71 Math.max(1e-6, 72 Math.min(Math.abs(expectedArray[i]), 73 Math.abs(actualArray[i]))); 74 assert_less_than(error, 1e-5, `comparing (at index ${i} actual value "${actual}" [${actualArray[i]}] and expected value "${expected}" [${expectedArray[i]}]`); 75 } 76 }; 77 78 // The spec at 79 // https://drafts.csswg.org/css-transforms-2/#interpolation-of-transform-functions 80 // requires that perspective be interpolated by decomposing the matrix 81 // (which is trivial for perspective) and then interpolating the pieces. 82 // The piece that's interpolated (the z part of the perspective array) 83 // contains the negative reciprocal of the argument to perspective(). 84 const interpolatePerspective = (from, to, progress) => { 85 return 1.0/((1.0 - progress) * (1.0/from) + progress * (1.0/to)); 86 }; 87 88 // Perspective 89 test_interpolation({ 90 property: 'transform', 91 from: 'perspective(400px)', 92 to: 'perspective(500px)', 93 comparisonFunction: compareWithPerspective 94 }, [ 95 {at: -1, expect: `perspective(${interpolatePerspective(400, 500, -1)}px)`}, 96 {at: 0, expect: `perspective(${interpolatePerspective(400, 500, 0)}px)`}, 97 {at: 0.25, expect: `perspective(${interpolatePerspective(400, 500, 0.25)}px)`}, 98 {at: 0.75, expect: `perspective(${interpolatePerspective(400, 500, 0.75)}px)`}, 99 {at: 1, expect: `perspective(${interpolatePerspective(400, 500, 1)}px)`}, 100 {at: 2, expect: `perspective(${interpolatePerspective(400, 500, 2)}px)`}, 101 ]); 102 test_interpolation({ 103 property: 'transform', 104 from: 'skewX(10rad) perspective(400px)', 105 to: 'skewX(20rad) perspective(500px)', 106 comparisonFunction: compareWithPerspective 107 }, [ 108 {at: -1, expect: `skewX(0rad) perspective(${interpolatePerspective(400, 500, -1)}px)`}, 109 {at: 0, expect: `skewX(10rad) perspective(${interpolatePerspective(400, 500, 0)}px)`}, 110 {at: 0.25, expect: `skewX(12.5rad) perspective(${interpolatePerspective(400, 500, 0.25)}px)`}, 111 {at: 0.75, expect: `skewX(17.5rad) perspective(${interpolatePerspective(400, 500, 0.75)}px)`}, 112 {at: 1, expect: `skewX(20rad) perspective(${interpolatePerspective(400, 500, 1)}px)`}, 113 {at: 2, expect: `skewX(30rad) perspective(${interpolatePerspective(400, 500, 2)}px)`}, 114 ]); 115 test_interpolation({ 116 property: 'transform', 117 from: 'scaleZ(1) perspective(400px)', 118 to: 'scaleZ(2) perspective(500px)', 119 comparisonFunction: compareWithPerspective 120 }, [ 121 {at: -1, expect: `scaleZ(0) perspective(${interpolatePerspective(400, 500, -1)}px)`}, 122 {at: 0, expect: `scaleZ(1.0) perspective(${interpolatePerspective(400, 500, 0)}px)`}, 123 {at: 0.25, expect: `scaleZ(1.25) perspective(${interpolatePerspective(400, 500, 0.25)}px)`}, 124 {at: 0.75, expect: `scaleZ(1.75) perspective(${interpolatePerspective(400, 500, 0.75)}px)`}, 125 {at: 1, expect: `scaleZ(2) perspective(${interpolatePerspective(400, 500, 1)}px)`}, 126 {at: 2, expect: `scaleZ(3) perspective(${interpolatePerspective(400, 500, 2)}px)`}, 127 ]); 128 // Test that the transform identity function for perspective is perspective(none) 129 test_interpolation({ 130 property: 'transform', 131 from: 'scaleZ(2)', 132 to: 'scaleZ(2) perspective(500px)', 133 comparisonFunction: compareWithPerspective 134 }, [ 135 {at: -1, expect: `scaleZ(2)`}, 136 {at: 0, expect: `scaleZ(2)`}, 137 {at: 0.5, expect: `scaleZ(2) perspective(1000px)`}, 138 {at: 1, expect: `scaleZ(2) perspective(500px)`}, 139 {at: 2, expect: `scaleZ(2) perspective(250px)`}, 140 ]); 141 test_interpolation({ 142 property: 'transform', 143 from: 'perspective(none)', 144 to: 'perspective(500px)', 145 }, [ 146 {at: -1, expect: `perspective(none)`}, 147 {at: 0, expect: `perspective(none)`}, 148 {at: 0.5, expect: `perspective(1000px)`}, 149 {at: 1, expect: `perspective(500px)`}, 150 {at: 2, expect: `perspective(250px)`}, 151 ]); 152 153 // Rotate 154 test_interpolation({ 155 property: 'transform', 156 from: 'rotate(30deg)', 157 to: 'rotate(330deg)' 158 }, [ 159 {at: -1, expect: 'rotate(-270deg)'}, 160 {at: 0, expect: 'rotate(30deg)'}, 161 {at: 0.25, expect: 'rotate(105deg)'}, 162 {at: 0.75, expect: 'rotate(255deg)'}, 163 {at: 1, expect: 'rotate(330deg)'}, 164 {at: 2, expect: 'rotate(630deg)'}, 165 ]); 166 test_interpolation({ 167 property: 'transform', 168 from: 'rotateX(0deg)', 169 to: 'rotateX(700deg)' 170 }, [ 171 {at: -1, expect: 'rotateX(-700deg)'}, 172 {at: 0, expect: 'rotateX(0deg)'}, 173 {at: 0.25, expect: 'rotateX(175deg)'}, 174 {at: 0.75, expect: 'rotateX(525deg)'}, 175 {at: 1, expect: 'rotateX(700deg)'}, 176 {at: 2, expect: 'rotateX(1400deg)'}, 177 ]); 178 test_interpolation({ 179 property: 'transform', 180 from: 'rotateY(0deg)', 181 to: 'rotateY(800deg)' 182 }, [ 183 {at: -1, expect: 'rotateY(-800deg)'}, 184 {at: 0, expect: 'rotateY(0deg)'}, 185 {at: 0.25, expect: 'rotateY(200deg)'}, 186 {at: 0.75, expect: 'rotateY(600deg)'}, 187 {at: 1, expect: 'rotateY(800deg)'}, 188 {at: 2, expect: 'rotateY(1600deg)'}, 189 ]); 190 test_interpolation({ 191 property: 'transform', 192 from: 'rotateZ(0deg)', 193 to: 'rotateZ(900deg)' 194 }, [ 195 {at: -1, expect: 'rotateZ(-900deg)'}, 196 {at: 0, expect: 'rotateZ(0deg)'}, 197 {at: 0.25, expect: 'rotateZ(225deg)'}, 198 {at: 0.75, expect: 'rotateZ(675deg)'}, 199 {at: 1, expect: 'rotateZ(900deg)'}, 200 {at: 2, expect: 'rotateZ(1800deg)'}, 201 ]); 202 // Interpolation is about a common axis if either endpoint has a rotation angle 203 // of zero. 204 test_interpolation({ 205 property: 'transform', 206 from: 'rotateX(0deg)', 207 to: 'rotateY(900deg)' 208 }, [ 209 {at: -1, expect: 'rotateY(-900deg)'}, 210 {at: 0, expect: 'rotateY(0deg)'}, 211 {at: 0.25, expect: 'rotateY(225deg)'}, 212 {at: 0.75, expect: 'rotateY(675deg)'}, 213 {at: 1, expect: 'rotateY(900deg)'}, 214 {at: 2, expect: 'rotateY(1800deg)'}, 215 ]); 216 test_interpolation({ 217 property: 'transform', 218 from: 'rotateY(900deg)', 219 to: 'rotateZ(0deg)' 220 }, [ 221 {at: -1, expect: 'rotateY(1800deg)'}, 222 {at: 0, expect: 'rotateY(900deg)'}, 223 {at: 0.25, expect: 'rotateY(675deg)'}, 224 {at: 0.75, expect: 'rotateY(225deg)'}, 225 {at: 1, expect: 'rotateY(0deg)'}, 226 {at: 2, expect: 'rotateY(-900deg)'}, 227 ]); 228 test_interpolation({ 229 property: 'transform', 230 from: 'rotate3d(7, 8, 9, 100deg)', 231 to: 'rotate3d(7, 8, 9, 260deg)' 232 }, [ 233 {at: -1, expect: 'rotate3d(7, 8, 9, -60deg)'}, 234 {at: 0, expect: 'rotate3d(7, 8, 9, 100deg)'}, 235 {at: 0.25, expect: 'rotate3d(7, 8, 9, 140deg)'}, 236 {at: 0.75, expect: 'rotate3d(7, 8, 9, 220deg)'}, 237 {at: 1, expect: 'rotate3d(7, 8, 9, 260deg)'}, 238 {at: 2, expect: 'rotate3d(7, 8, 9, 420deg)'}, 239 ]); 240 test_interpolation({ 241 property: 'transform', 242 from: 'rotate3d(7, 8, 9, 0deg)', 243 to: 'rotate3d(7, 8, 9, 450deg)' 244 }, [ 245 {at: -1, expect: 'rotate3d(7, 8, 9, -450deg)'}, 246 {at: 0, expect: 'rotate3d(7, 8, 9, 0deg)'}, 247 {at: 0.25, expect: 'rotate3d(7, 8, 9, 112.5deg)'}, 248 {at: 0.75, expect: 'rotate3d(7, 8, 9, 337.5deg)'}, 249 {at: 1, expect: 'rotate3d(7, 8, 9, 450deg)'}, 250 {at: 2, expect: 'rotate3d(7, 8, 9, 900deg)'}, 251 ]); 252 test_interpolation({ 253 property: 'transform', 254 from: 'rotate3d(0, 1, 0, 0deg)', 255 to: 'rotate3d(0, 1, 0, 450deg)' 256 }, [ 257 {at: -1, expect: 'rotate3d(0, 1, 0, -450deg)'}, 258 {at: 0, expect: 'rotate3d(0, 1, 0, 0deg)'}, 259 {at: 0.25, expect: 'rotate3d(0, 1, 0, 112.5deg)'}, 260 {at: 0.75, expect: 'rotate3d(0, 1, 0, 337.5deg)'}, 261 {at: 1, expect: 'rotate3d(0, 1, 0, 450deg)'}, 262 {at: 2, expect: 'rotate3d(0, 1, 0, 900deg)'}, 263 ]); 264 // Rotation is about a common axis if the axes are colinear. 265 test_interpolation({ 266 property: 'transform', 267 from: 'rotate3d(0, 1, 0, 0deg)', 268 to: 'rotate3d(0, 2, 0, 450deg)' 269 }, [ 270 {at: -1, expect: 'rotate3d(0, 1, 0, -450deg)'}, 271 {at: 0, expect: 'rotate3d(0, 1, 0, 0deg)'}, 272 {at: 0.25, expect: 'rotate3d(0, 1, 0, 112.5deg)'}, 273 {at: 0.75, expect: 'rotate3d(0, 1, 0, 337.5deg)'}, 274 {at: 1, expect: 'rotate3d(0, 1, 0, 450deg)'}, 275 {at: 2, expect: 'rotate3d(0, 1, 0, 900deg)'}, 276 ]); 277 test_interpolation({ 278 property: 'transform', 279 from: 'rotate3d(1, 1, 0, 90deg)', 280 to: 'rotate3d(0, 1, 1, 180deg)' 281 }, [ 282 {at: -1, expect: 'rotate3d(0.41, -0.41, -0.82, 120deg)'}, 283 {at: 0, expect: 'rotate3d(1, 1, 0, 90deg)'}, 284 {at: 0.25, expect: 'rotate3d(0.524083, 0.804261, 0.280178, 106.91deg)'}, 285 {at: 0.75, expect: 'rotate3d(0.163027, 0.774382, 0.611354, 153.99deg)'}, 286 {at: 1, expect: 'rotate3d(0, 1, 1, 180deg)'}, 287 {at: 2, expect: 'rotate3d(0.71, 0, -0.71, 90deg)'}, 288 ]); 289 test_interpolation({ 290 property: 'transform', 291 from: 'none', 292 to: 'rotate(90deg)' 293 }, [ 294 {at: -1, expect: 'rotate(-90deg)'}, 295 {at: 0, expect: 'rotate(0deg)'}, 296 {at: 0.25, expect: 'rotate(22.5deg)'}, 297 {at: 0.75, expect: 'rotate(67.5deg)'}, 298 {at: 1, expect: 'rotate(90deg)'}, 299 {at: 2, expect: 'rotate(180deg)'}, 300 ]); 301 test_interpolation({ 302 property: 'transform', 303 from: 'rotate(90deg)', 304 to: 'none' 305 }, [ 306 {at: -1, expect: 'rotate(180deg)'}, 307 {at: 0, expect: 'rotate(90deg)'}, 308 {at: 0.25, expect: 'rotate(67.5deg)'}, 309 {at: 0.75, expect: 'rotate(22.5deg)'}, 310 {at: 1, expect: 'rotate(0deg)'}, 311 {at: 2, expect: 'rotate(-90deg)'}, 312 ]); 313 test_interpolation({ 314 property: 'transform', 315 from: 'rotateX(0deg) rotateY(0deg) rotateZ(0deg)', 316 to: 'rotateX(700deg) rotateY(800deg) rotateZ(900deg)' 317 }, [ 318 {at: -1, expect: 'rotateX(-700deg) rotateY(-800deg) rotateZ(-900deg)'}, 319 {at: 0, expect: 'rotateX(0deg) rotateY(0deg) rotateZ(0deg)'}, 320 {at: 0.25, expect: 'rotateX(175deg) rotateY(200deg) rotateZ(225deg)'}, 321 {at: 0.75, expect: 'rotateX(525deg) rotateY(600deg) rotateZ(675deg)'}, 322 {at: 1, expect: 'rotateX(700deg) rotateY(800deg) rotateZ(900deg)'}, 323 {at: 2, expect: 'rotateX(1400deg) rotateY(1600deg) rotateZ(1800deg)'}, 324 ]); 325 </script>