1 /* 2 * Copyright © 2009 Apple Inc. All rights reserved. 3 */ 4 5 /** 6 * @class 7 * @name TKTransitionDelegate 8 * @since TuneKit 1.0 9 */ 10 11 /** 12 * Invoked when a transition has completed. 13 * 14 * @name transitionDidComplete 15 * @function 16 * 17 * @param {TKTransition} theTransition The transition that just completed. 18 * @memberOf TKTransitionDelegate.prototype 19 */ 20 21 const TKTransitionDidCompleteDelegate = 'transitionDidComplete'; 22 23 const TKTransitionDefaults = { 24 duration : 0.5, 25 delay : 0, 26 removesTargetUponCompletion : false, 27 revertsToOriginalValues : false 28 }; 29 30 const TKTransitionStyles = ['-webkit-transition-property', '-webkit-transition-duration', '-webkit-transition-timing-function', '-webkit-transition-delay', '-webkit-transition']; 31 32 /* ==================== TKTransition ==================== */ 33 34 /** 35 * @class 36 * 37 * <p>The TKTransition class allows to create a synchronized change of one or more property values on a 38 * {@link Element} instance or any DOM element.</p> 39 * 40 * <p>First, the user sets the {@link #target} for the transition, 41 * identifying the target element, and second the set of {@link #properties}, in the form of an <code>Array</code> 42 * of CSS properties for the element. Then the {@link #to} and optional 43 * {@link #from} value arrays are provided to define what values will be used for the transition. Each item in the 44 * {@link #from} and {@link #to} arrays matches the item at the same index in the {@link #properties} array, and the same 45 * applies to the {@link #duration} property, which can also be a single value shared for all transitioned properties.</p> 46 * 47 * <p>Finally, once the properties on the transition are all set, the {@link #start} method must be called so that the 48 * transition is started. Note that it is possible to group transitions within a {@link TKTransaction} and set a default 49 * set of transition properties within a transaction using the {@link TKTransaction.defaults} static property.</p> 50 * 51 * <p>The following example shows how to make a transition that fades an element in as it animates from the right side of the screen:</p> 52 * 53 <pre class="example"> 54 new TKTransition({ 55 target : anElement, 56 properties : ['opacity', '-webkit-transform']; 57 from : [0, 'translate(320px, 0)'], 58 to : [1, 'translate(0, 0)'] 59 duration : 0.5; 60 }).start(); 61 </pre> 62 * 63 * <p>Note that properties of a {@link TKTransition} object can be passed as a batch to the {@link TKTransition} constructor 64 * as an anonymous object. This also allows reuse of a set of parameters across several transitions. Also, a set of pre-baked 65 * transitions exist can be easily applied to an element with the {@link Element#applyTransition} method.</p> 66 * 67 * @since TuneKit 1.0 68 */ 69 function TKTransition (params) { 70 // set up defaults 71 /** 72 * The transition target. 73 * @type Element 74 */ 75 this.target = null; 76 /** 77 * The set of properties that will be transitioned. The properties are CSS properties of the targeted <code>Element</code>. 78 * @type Array 79 */ 80 this.properties = null; 81 /** 82 * The set of durations in seconds, each duration matching a property in the {@link #properties} array. 83 * Note that it's possible to specify a single value instead of an array to use the same duration for 84 * all properties. 85 * @type Object 86 */ 87 this.duration = null; 88 /** 89 * The set of delays in seconds, each delay matching a property in the {@link #properties} array. 90 * Note that it's possible to specify a single delay instead of an array to use the same delay for 91 * all properties. 92 * @type Object 93 */ 94 this.delay = null; 95 /** 96 * Optional list of values to start the transition from. Each value in this array must match the property 97 * at the same index in the {@link #properties} array. 98 * @type Array 99 */ 100 this.from = null; 101 /** 102 * Required list of values to transition to. Each value in this array must match the property 103 * at the same index in the {@link #properties} array. 104 * @type Array 105 */ 106 this.to = null; 107 /** 108 * The set of timing functions, each timing function matching a property in the {@link #properties} 109 * array. Note that it's possible to specify a single timing function instead of an array to use the 110 * same timing function for all properties. 111 * @type Object 112 */ 113 this.timingFunction = null; 114 /** 115 * The delegate object that implements the {@link TKTransitionDelegate} protocol. 116 * @type Object 117 */ 118 this.delegate = null; 119 /** 120 * Indicates whether the target needs to be removed once the transition completes. Defaults to <code>false</code>. 121 * @type bool 122 */ 123 this.removesTargetUponCompletion = null; 124 /** 125 * Indicates whether the target needs to reset the property that are transitioned to their original values 126 * once the transition completes. Defaults to <code>false</code>. 127 * @type bool 128 */ 129 this.revertsToOriginalValues = null; 130 // 131 this.defaultsApplied = false; 132 this.archivedStyles = null; 133 this.archivedValues = []; 134 // import params 135 TKUtils.copyPropertiesFromSourceToTarget(params, this); 136 }; 137 138 /* ==================== Dealing with defaults ==================== */ 139 140 TKTransition.prototype.applyDefaults = function () { 141 if (this.defaultsApplied) { 142 return; 143 } 144 // 145 for (var i in TKTransitionDefaults) { 146 if (this[i] === null) { 147 this[i] = TKTransitionDefaults[i]; 148 } 149 } 150 // 151 this.defaultsApplied = true; 152 }; 153 154 /* ==================== Archiving the transition styles ==================== */ 155 156 TKTransition.prototype.getTargetStyle = function () { 157 // if (this.target.style === null) { 158 // this.target.setAttribute('style', 'foo: bar'); 159 // } 160 return this.target.style; 161 }; 162 163 TKTransition.prototype.archiveTransitionStyles = function () { 164 // do nothing if we already have archived styles in this run session 165 if (this.archivedStyles !== null) { 166 return; 167 } 168 // iterate all TKTransitionStyles and archive them 169 this.archivedStyles = []; 170 var style = this.getTargetStyle(); 171 for (var i = 0; i < TKTransitionStyles.length; i++) { 172 this.archivedStyles.push(style.getPropertyValue(TKTransitionStyles[i])); 173 } 174 }; 175 176 TKTransition.prototype.restoreTransitionStyles = function () { 177 var style = this.getTargetStyle(); 178 // iterate all TKTransitionStyles and restore them 179 for (var i = 0; i < TKTransitionStyles.length; i++) { 180 style.setProperty(TKTransitionStyles[i], this.archivedStyles[i], ''); 181 } 182 // reset archived styles 183 this.archivedStyles = null; 184 }; 185 186 /* ==================== Archiving the base values ==================== */ 187 188 TKTransition.prototype.archiveBaseValues = function () { 189 // do nothing if we don't need to archive base values 190 if (!this.revertsToOriginalValues) { 191 return; 192 } 193 var style = this.getTargetStyle(); 194 for (var i = 0; i < this.properties.length; i++) { 195 this.archivedValues.push(style.getPropertyValue(this.properties[i])); 196 } 197 }; 198 199 TKTransition.prototype.restoreBaseValues = function () { 200 var style = this.getTargetStyle(); 201 for (var i = 0; i < this.properties.length; i++) { 202 style.setProperty(this.properties[i], this.archivedValues[i], null); 203 } 204 }; 205 206 /* ==================== Starting the transition ==================== */ 207 208 /** 209 * Starts the transition. 210 */ 211 TKTransition.prototype.start = function () { 212 // if we have an active transaction, just add to it 213 if (TKTransaction.openTransactions > 0) { 214 TKTransaction.addTransition(this); 215 return; 216 } 217 // otherwise, we'll just get it going 218 this.applyDefaults(); 219 if (this.from === null) { 220 this.applyToState(); 221 } 222 else { 223 this.applyFromState(); 224 var _this = this; 225 setTimeout(function () { 226 _this.applyToState(); 227 }, 0); 228 } 229 }; 230 231 /* ==================== Applying the "from" state ==================== */ 232 233 TKTransition.prototype.applyFromState = function () { 234 // do nothing if we have no "from" state specified 235 if (this.from === null) { 236 return; 237 } 238 // 239 this.applyDefaults(); 240 this.archiveTransitionStyles(); 241 var style = this.getTargetStyle(); 242 style.webkitTransitionDuration = 0; 243 for (var i = 0; i < this.properties.length; i++) { 244 style.setProperty(this.properties[i], this.from[i], ''); 245 } 246 }; 247 248 /* ==================== Applying the "to" state ==================== */ 249 250 TKTransition.prototype.applyToState = function () { 251 // first, make sure we have defaults applied if some 252 // properties are not explicitely set on our transition 253 this.applyDefaults(); 254 255 // and that we archive the transition styles to be reverted 256 this.archiveTransitionStyles(); 257 258 // and that we archive the original values 259 this.archiveBaseValues(); 260 261 // now compile the styles needed for this transition 262 this.cssProperties = []; 263 var transition_styles = []; 264 for (var i = 0; i < this.properties.length; i++) { 265 var property = this.properties[i]; 266 // do nothing if we already have an animation defined for this 267 if (this.cssProperties.indexOf(property) > -1) { 268 continue; 269 } 270 // else, set up the CSS style for this transition 271 var duration = (TKUtils.objectIsArray(this.duration)) ? this.duration[i] : this.duration; 272 var timing = (TKUtils.objectIsArray(this.timingFunction)) ? this.timingFunction[i] : this.timingFunction; 273 var delay = (TKUtils.objectIsArray(this.delay)) ? this.delay[i] : this.delay; 274 transition_styles.push([property, duration + 's', timing, delay + 's'].join(' ')); 275 // and remember we are animating this property 276 this.cssProperties.push(property); 277 } 278 279 var style = this.getTargetStyle(); 280 for (var i = 0; i < this.properties.length; i++) { 281 style.setProperty(this.properties[i], this.to[i], ''); 282 } 283 284 // set up the transition styles 285 style.webkitTransition = transition_styles.join(', '); 286 287 // register for events to track transition completions 288 this.target.addEventListener('webkitTransitionEnd', this, false); 289 this.completedTransitions = 0; 290 }; 291 292 /* ==================== Tracking transition completion ==================== */ 293 294 // XXX: we won't be getting an event for properties that have the same value in the to 295 // state, so we'll need to do a little work to track css properties that won't really transition 296 TKTransition.prototype.handleEvent = function (event) { 297 // do nothing if that event just bubbled from our target's sub-tree 298 if (event.currentTarget !== this.target) { 299 return; 300 } 301 302 // update the completion counter 303 this.completedTransitions++; 304 305 // do nothing if we still have running transitions 306 if (this.completedTransitions != this.cssProperties.length) { 307 return; 308 } 309 310 // the counter reached our properties count, fire up the delegate 311 if (TKUtils.objectHasMethod(this.delegate, TKTransitionDidCompleteDelegate)) { 312 this.delegate[TKTransitionDidCompleteDelegate](this); 313 } 314 315 // remove from the tree if instructed to do so 316 if (this.removesTargetUponCompletion) { 317 this.target.parentNode.removeChild(this.target); 318 } 319 // otherwise, restore transition styles 320 else { 321 this.restoreTransitionStyles(); 322 } 323 324 // restore original values if instructed to do so 325 if (this.revertsToOriginalValues) { 326 this.restoreBaseValues(); 327 } 328 }; 329 330 TKClass(TKTransition); 331