1 // Copyright (c) 2012 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 5 // Counter used to give webkit animations unique names. 6 var animationCounter = 0; 7 8 var animationEventTracker_ = new EventTracker(); 9 10 function addAnimation(code) { 11 var name = 'anim' + animationCounter; 12 animationCounter++; 13 var rules = document.createTextNode( 14 '@-webkit-keyframes ' + name + ' {' + code + '}'); 15 var el = document.createElement('style'); 16 el.type = 'text/css'; 17 el.appendChild(rules); 18 el.setAttribute('id', name); 19 document.body.appendChild(el); 20 21 return name; 22 } 23 24 /** 25 * Generates css code for fading in an element by animating the height. 26 * @param {number} targetHeight The desired height in pixels after the animation 27 * ends. 28 * @return {string} The css code for the fade in animation. 29 */ 30 function getFadeInAnimationCode(targetHeight) { 31 return '0% { opacity: 0; height: 0; } ' + 32 '80% { opacity: 0.5; height: ' + (targetHeight + 4) + 'px; }' + 33 '100% { opacity: 1; height: ' + targetHeight + 'px; }'; 34 } 35 36 /** 37 * Fades in an element. Used for both printing options and error messages 38 * appearing underneath the textfields. 39 * @param {HTMLElement} el The element to be faded in. 40 * @param {boolean=} opt_justShow Whether {@code el} should be shown with no 41 * animation. 42 */ 43 function fadeInElement(el, opt_justShow) { 44 if (el.classList.contains('visible')) 45 return; 46 el.classList.remove('closing'); 47 el.hidden = false; 48 el.setAttribute('aria-hidden', 'false'); 49 el.style.height = 'auto'; 50 var height = el.offsetHeight; 51 if (opt_justShow) { 52 el.style.height = ''; 53 el.style.opacity = ''; 54 } else { 55 el.style.height = height + 'px'; 56 var animName = addAnimation(getFadeInAnimationCode(height)); 57 animationEventTracker_.add( 58 el, 'webkitAnimationEnd', onFadeInAnimationEnd.bind(el), false); 59 el.style.webkitAnimationName = animName; 60 } 61 el.classList.add('visible'); 62 } 63 64 /** 65 * Fades out an element. Used for both printing options and error messages 66 * appearing underneath the textfields. 67 * @param {HTMLElement} el The element to be faded out. 68 */ 69 function fadeOutElement(el) { 70 if (!el.classList.contains('visible')) 71 return; 72 fadeInAnimationCleanup(el); 73 el.style.height = 'auto'; 74 var height = el.offsetHeight; 75 el.style.height = height + 'px'; 76 el.offsetHeight; // Should force an update of the computed style. 77 animationEventTracker_.add( 78 el, 'webkitTransitionEnd', onFadeOutTransitionEnd.bind(el), false); 79 el.classList.add('closing'); 80 el.classList.remove('visible'); 81 el.setAttribute('aria-hidden', 'true'); 82 } 83 84 /** 85 * Executes when a fade out animation ends. 86 * @param {WebkitTransitionEvent} event The event that triggered this listener. 87 * @this {HTMLElement} The element where the transition occurred. 88 */ 89 function onFadeOutTransitionEnd(event) { 90 if (event.propertyName != 'height') 91 return; 92 animationEventTracker_.remove(this, 'webkitTransitionEnd'); 93 this.hidden = true; 94 } 95 96 /** 97 * Executes when a fade in animation ends. 98 * @param {WebkitAnimationEvent} event The event that triggered this listener. 99 * @this {HTMLElement} The element where the transition occurred. 100 */ 101 function onFadeInAnimationEnd(event) { 102 this.style.height = ''; 103 fadeInAnimationCleanup(this); 104 } 105 106 /** 107 * Removes the <style> element corresponding to |animationName| from the DOM. 108 * @param {HTMLElement} element The animated element. 109 */ 110 function fadeInAnimationCleanup(element) { 111 if (element.style.webkitAnimationName) { 112 var animEl = document.getElementById(element.style.webkitAnimationName); 113 if (animEl) 114 animEl.parentNode.removeChild(animEl); 115 element.style.webkitAnimationName = ''; 116 animationEventTracker_.remove(element, 'webkitAnimationEnd'); 117 } 118 } 119 120 /** 121 * Fades in a printing option existing under |el|. 122 * @param {HTMLElement} el The element to hide. 123 * @param {boolean=} opt_justShow Whether {@code el} should be hidden with no 124 * animation. 125 */ 126 function fadeInOption(el, opt_justShow) { 127 if (el.classList.contains('visible')) 128 return; 129 // To make the option visible during the first fade in. 130 el.hidden = false; 131 132 var leftColumn = el.querySelector('.left-column'); 133 wrapContentsInDiv(leftColumn, ['invisible']); 134 var rightColumn = el.querySelector('.right-column'); 135 wrapContentsInDiv(rightColumn, ['invisible']); 136 137 var toAnimate = el.querySelectorAll('.collapsible'); 138 for (var i = 0; i < toAnimate.length; i++) 139 fadeInElement(toAnimate[i], opt_justShow); 140 el.classList.add('visible'); 141 } 142 143 /** 144 * Fades out a printing option existing under |el|. 145 * @param {HTMLElement} el The element to hide. 146 * @param {boolean=} opt_justHide Whether {@code el} should be hidden with no 147 * animation. 148 */ 149 function fadeOutOption(el, opt_justHide) { 150 if (!el.classList.contains('visible')) 151 return; 152 153 var leftColumn = el.querySelector('.left-column'); 154 wrapContentsInDiv(leftColumn, ['visible']); 155 var rightColumn = el.querySelector('.right-column'); 156 wrapContentsInDiv(rightColumn, ['visible']); 157 158 var toAnimate = el.querySelectorAll('.collapsible'); 159 for (var i = 0; i < toAnimate.length; i++) { 160 if (opt_justHide) { 161 toAnimate[i].hidden = true; 162 toAnimate[i].classList.add('closing'); 163 toAnimate[i].classList.remove('visible'); 164 } else { 165 fadeOutElement(toAnimate[i]); 166 } 167 } 168 el.classList.remove('visible'); 169 } 170 171 /** 172 * Wraps the contents of |el| in a div element and attaches css classes 173 * |classes| in the new div, only if has not been already done. It is necessary 174 * for animating the height of table cells. 175 * @param {HTMLElement} el The element to be processed. 176 * @param {array} classes The css classes to add. 177 */ 178 function wrapContentsInDiv(el, classes) { 179 var div = el.querySelector('div'); 180 if (!div || !div.classList.contains('collapsible')) { 181 div = document.createElement('div'); 182 while (el.childNodes.length > 0) 183 div.appendChild(el.firstChild); 184 el.appendChild(div); 185 } 186 187 div.className = ''; 188 div.classList.add('collapsible'); 189 for (var i = 0; i < classes.length; i++) 190 div.classList.add(classes[i]); 191 } 192