transform-matrix-composition.html (7899B)
1 <!DOCTYPE html> 2 <meta charset="UTF-8"> 3 <title>transform-matrix composition</title> 4 <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#ctm"> 5 <meta name="assert" content="transform-matrix supports animation as a transform list"> 6 7 <script src="/resources/testharness.js"></script> 8 <script src="/resources/testharnessreport.js"></script> 9 <script src="/css/support/interpolation-testcommon.js"></script> 10 11 <body> 12 <script> 13 // For matrix and matrix3d, addition is defined as concatenation whilst 14 // accumulation works by decomposing the matrix and then accumulating the 15 // decomposed functions. We can therefore test the difference between the 16 // two by mixing functions such that a naive multiplication would look 17 // different than the accumulation behavior. 18 // 19 // Note that due to the complexities of decomposition the test space here is 20 // huge; we cover some basic cases and hope that the tests for the individual 21 // functions provide a lot of the remaining coverage. 22 23 // Creates a matrix3d function, encoding the passed rotation and translation. 24 // Note that the translate will not be affected by the rotation. 25 function create3dMatrix(x, y, z, radians, translateX) { 26 // Normalize the rotation axes. 27 const length = Math.sqrt(x*x + y*y + z*z); 28 x /= length; 29 y /= length; 30 z /= length; 31 32 const sc = Math.sin(radians / 2) * Math.cos(radians / 2); 33 const sq = Math.sin(radians / 2) * Math.sin(radians / 2); 34 35 // https://drafts.csswg.org/css-transforms-2/#Rotate3dDefined 36 // https://drafts.csswg.org/css-transforms-2/#Translate3dDefined 37 return 'matrix3d(' + [ 38 1 - 2 * (y*y + z*z) * sq, 39 2 * (x * y * sq + z * sc), 40 2 * (x * z * sq - y * sc), 41 0, 42 2 * (x * y * sq - z * sc), 43 1 - 2 * (x*x + z*z) * sq, 44 2 * (y * z * sq + x * sc), 45 0, 46 2 * (x * z * sq + y * sc), 47 2 * (y * z * sq - x * sc), 48 1 - 2 * (x*x + y*y) * sq, 49 0, 50 translateX, 0, 0, 1].join() + ')'; 51 } 52 53 // ------------ Addition tests -------------- 54 55 test_composition({ 56 property: 'transform', 57 // translateX(100px) rotate(90deg) 58 underlying: 'matrix(0, 1, -1, 0, 100, 0)', 59 // translateX(100px) 60 addFrom: 'matrix(1, 0, 0, 1, 100, 0)', 61 // translateX(200px) 62 addTo: 'matrix(1, 0, 0, 1, 200, 0)', 63 }, [ 64 {at: -0.5, expect: 'matrix(0, 1, -1, 0, 100, 50)'}, 65 {at: 0, expect: 'matrix(0, 1, -1, 0, 100, 100)'}, 66 {at: 0.25, expect: 'matrix(0, 1, -1, 0, 100, 125)'}, 67 {at: 0.5, expect: 'matrix(0, 1, -1, 0, 100, 150)'}, 68 {at: 0.75, expect: 'matrix(0, 1, -1, 0, 100, 175)'}, 69 {at: 1, expect: 'matrix(0, 1, -1, 0, 100, 200)'}, 70 {at: 1.5, expect: 'matrix(0, 1, -1, 0, 100, 250)'}, 71 ]); 72 73 test_composition({ 74 property: 'transform', 75 // translateX(100px) rotate3d(1, 1, 0, 45deg) 76 underlying: create3dMatrix(1, 1, 0, Math.PI / 4, 100), 77 // translateX(100px) 78 addFrom: 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 100, 0, 0, 1)', 79 // translateX(200px) 80 addTo: 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 200, 0, 0, 1)', 81 }, [ 82 // matrix3ds are hard to read; these are the decomposed forms for clarity 83 {at: -0.5, expect: 'translateX(100px) rotate3d(1, 1, 0, 45deg) translateX(50px)'}, 84 {at: 0, expect: 'translateX(100px) rotate3d(1, 1, 0, 45deg) translateX(100px)'}, 85 {at: 0.25, expect: 'translateX(100px) rotate3d(1, 1, 0, 45deg) translateX(125px)'}, 86 {at: 0.5, expect: 'translateX(100px) rotate3d(1, 1, 0, 45deg) translateX(150px)'}, 87 {at: 0.75, expect: 'translateX(100px) rotate3d(1, 1, 0, 45deg) translateX(175px)'}, 88 {at: 1, expect: 'translateX(100px) rotate3d(1, 1, 0, 45deg) translateX(200px)'}, 89 {at: 1.5, expect: 'translateX(100px) rotate3d(1, 1, 0, 45deg) translateX(250px)'}, 90 ]); 91 92 // Addition of non-invertible matrices is still defined as concatenation so 93 // includes the underlying value. 94 95 test_composition({ 96 property: 'transform', 97 // Non-invertible. 98 underlying: 'matrix(1, 1, 0, 0, 0, 100)', 99 // translateX(100px) 100 addFrom: 'matrix(1, 0, 0, 1, 100, 0)', 101 // translateX(200px) 102 addTo: 'matrix(1, 0, 0, 1, 200, 0)', 103 }, [ 104 {at: -0.5, expect: 'matrix(1, 1, 0, 0, 100, 200)'}, 105 {at: 0, expect: 'matrix(1, 1, 0, 0, 100, 200)'}, 106 {at: 0.25, expect: 'matrix(1, 1, 0, 0, 100, 200)'}, 107 {at: 0.5, expect: 'matrix(1, 1, 0, 0, 200, 300)'}, 108 {at: 0.75, expect: 'matrix(1, 1, 0, 0, 200, 300)'}, 109 {at: 1, expect: 'matrix(1, 1, 0, 0, 200, 300)'}, 110 {at: 1.5, expect: 'matrix(1, 1, 0, 0, 200, 300)'}, 111 ]); 112 113 test_composition({ 114 property: 'transform', 115 // translateX(100px) 116 underlying: 'matrix(1, 0, 0, 1, 100, 0)', 117 // Non-invertible 118 addFrom: 'matrix(1, 1, 0, 0, 0, 100)', 119 // translateX(200px) 120 addTo: 'matrix(1, 0, 0, 1, 200, 0)', 121 }, [ 122 {at: -0.5, expect: 'matrix(1, 1, 0, 0, 100, 100)'}, 123 {at: 0, expect: 'matrix(1, 1, 0, 0, 100, 100)'}, 124 {at: 0.25, expect: 'matrix(1, 1, 0, 0, 100, 100)'}, 125 {at: 0.5, expect: 'matrix(1, 0, 0, 1, 300, 0)'}, 126 {at: 0.75, expect: 'matrix(1, 0, 0, 1, 300, 0)'}, 127 {at: 1, expect: 'matrix(1, 0, 0, 1, 300, 0)'}, 128 {at: 1.5, expect: 'matrix(1, 0, 0, 1, 300, 0)'}, 129 ]); 130 131 // ------------ Accumulation tests -------------- 132 133 test_composition({ 134 property: 'transform', 135 // translateX(100px) rotate(90deg) 136 underlying: 'matrix(0, 1, -1, 0, 100, 0)', 137 // translateX(100px) 138 accumulateFrom: 'matrix(1, 0, 0, 1, 100, 0)', 139 // translateX(200px) 140 accumulateTo: 'matrix(1, 0, 0, 1, 200, 0)', 141 }, [ 142 {at: -0.5, expect: 'matrix(0, 1, -1, 0, 150, 0)'}, 143 {at: 0, expect: 'matrix(0, 1, -1, 0, 200, 0)'}, 144 {at: 0.25, expect: 'matrix(0, 1, -1, 0, 225, 0)'}, 145 {at: 0.5, expect: 'matrix(0, 1, -1, 0, 250, 0)'}, 146 {at: 0.75, expect: 'matrix(0, 1, -1, 0, 275, 0)'}, 147 {at: 1, expect: 'matrix(0, 1, -1, 0, 300, 0)'}, 148 {at: 1.5, expect: 'matrix(0, 1, -1, 0, 350, 0)'}, 149 ]); 150 151 test_composition({ 152 property: 'transform', 153 // translateX(100px) rotate3d(1, 1, 0, 45deg) 154 underlying: create3dMatrix(1, 1, 0, Math.PI / 4, 100), 155 // translateX(100px) 156 accumulateFrom: 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 100, 0, 0, 1)', 157 // translateX(200px) 158 accumulateTo: 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 200, 0, 0, 1)', 159 }, [ 160 // matrix3ds are hard to read; these are the decomposed forms for clarity 161 {at: -0.5, expect: 'translateX(150px) rotate3d(1, 1, 0, 45deg)'}, 162 {at: 0, expect: 'translateX(200px) rotate3d(1, 1, 0, 45deg)'}, 163 {at: 0.25, expect: 'translateX(225px) rotate3d(1, 1, 0, 45deg)'}, 164 {at: 0.5, expect: 'translateX(250px) rotate3d(1, 1, 0, 45deg)'}, 165 {at: 0.75, expect: 'translateX(275px) rotate3d(1, 1, 0, 45deg)'}, 166 {at: 1, expect: 'translateX(300px) rotate3d(1, 1, 0, 45deg)'}, 167 {at: 1.5, expect: 'translateX(350px) rotate3d(1, 1, 0, 45deg)'}, 168 ]); 169 170 // Accumulation of non-invertible matrices falls back to replace behavior. 171 172 test_composition({ 173 property: 'transform', 174 // Non-invertible. 175 underlying: 'matrix(1, 1, 0, 0, 0, 100)', 176 // translateX(100px) 177 accumulateFrom: 'matrix(1, 0, 0, 1, 100, 0)', 178 // translateX(200px) 179 accumulateTo: 'matrix(1, 0, 0, 1, 200, 0)', 180 }, [ 181 {at: -0.5, expect: 'matrix(1, 0, 0, 1, 50, 0)'}, 182 {at: 0, expect: 'matrix(1, 0, 0, 1, 100, 0)'}, 183 {at: 0.25, expect: 'matrix(1, 0, 0, 1, 125, 0)'}, 184 {at: 0.5, expect: 'matrix(1, 0, 0, 1, 150, 0)'}, 185 {at: 0.75, expect: 'matrix(1, 0, 0, 1, 175, 0)'}, 186 {at: 1, expect: 'matrix(1, 0, 0, 1, 200, 0)'}, 187 {at: 1.5, expect: 'matrix(1, 0, 0, 1, 250, 0)'}, 188 ]); 189 190 test_composition({ 191 property: 'transform', 192 // translateX(100px) 193 underlying: 'matrix(1, 0, 0, 1, 100, 0)', 194 // Non-invertible 195 accumulateFrom: 'matrix(1, 1, 0, 0, 0, 100)', 196 // translateX(200px) 197 accumulateTo: 'matrix(1, 0, 0, 1, 200, 0)', 198 }, [ 199 {at: -0.5, expect: 'matrix(1, 1, 0, 0, 0, 100)'}, 200 {at: 0, expect: 'matrix(1, 1, 0, 0, 0, 100)'}, 201 {at: 0.25, expect: 'matrix(1, 1, 0, 0, 0, 100)'}, 202 {at: 0.5, expect: 'matrix(1, 0, 0, 1, 300, 0)'}, 203 {at: 0.75, expect: 'matrix(1, 0, 0, 1, 300, 0)'}, 204 {at: 1, expect: 'matrix(1, 0, 0, 1, 300, 0)'}, 205 {at: 1.5, expect: 'matrix(1, 0, 0, 1, 300, 0)'}, 206 ]); 207 </script> 208 </body>