1// Copyright (c) 2011 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// This file contains functions that control several animations in the print 6// preview tab (scrollbars, showing hiding options, resizing). 7 8// Scrollbar will never get smaller than this 9const TRANSIENT_MIN_SCROLLBAR_SIZE = 40; 10// Timeout duration in milliseconds used for showing the scrollbars. 11const HIDE_SCROLLBARS_TIMEOUT = 250; 12// Timeout id used to reset the timer when needed. 13var transientHideScrollbarsTimeoutId = []; 14// Holds all elements that have scrollbars attached to them. 15var transientScrollbarEls = []; 16// Counter used to give webkit animations unique names. 17var animationCounter = 0; 18 19/** 20 * Makes the scrollbars visible. If |el| has already a scrollbar timer, it 21 * resets its value. 22 * 23 * @param {HTMLDivElement} el The element associated with the scrollbars 24 */ 25function showTransientScrollbars(el) { 26 el.scrollHorEl.classList.remove('invisible'); 27 el.scrollVertEl.classList.remove('invisible'); 28 29 if (el.transientHideScrollbarsTimeoutId) 30 window.clearTimeout(el.transientHideScrollbarsTimeoutId); 31 32 el.transientHideScrollbarsTimeoutId = 33 window.setTimeout(function() { hideTransientScrollbars(el) }, 34 HIDE_SCROLLBARS_TIMEOUT); 35} 36 37/** 38 * Hides the scrollbars. 39 * 40 * @param {HTMLElement} el The element associated with the scrollbars 41*/ 42function hideTransientScrollbars(el) { 43 el.scrollHorEl.classList.add('invisible'); 44 el.scrollVertEl.classList.add('invisible'); 45} 46 47/** 48 * Handles a mouse move event, takes care of updating the scrollbars. 49 * 50 * @param {event} event The event that triggered this handler 51*/ 52function handleTransientMouseMove(event) { 53 var el = event.target; 54 55 while (!el.classList.contains('scrollbar-inside') && el != document.body) 56 el = el.parentNode; 57 58 showTransientScrollbars(el); 59} 60 61/** 62 * Updates the scrollbars associated with the the input element. 63 * 64 * @param {HTMLElement} el 65 */ 66function updateTransientScrollbars(el) { 67 var scrollLeft = el.scrollLeft; 68 var scrollTop = el.scrollTop; 69 70 var scrollWidth = el.scrollWidth; 71 var scrollHeight = el.scrollHeight; 72 73 var offsetWidth = el.offsetWidth - el.scrollbarWidth; 74 var offsetHeight = el.offsetHeight - el.scrollbarHeight; 75 76 var elevatorWidth = offsetWidth / scrollWidth * offsetWidth; 77 var elevatorHeight = offsetHeight / scrollHeight * offsetHeight; 78 79 // Make sure the scrollbars are big enough. 80 if (elevatorWidth < TRANSIENT_MIN_SCROLLBAR_SIZE) 81 elevatorWidth = TRANSIENT_MIN_SCROLLBAR_SIZE; 82 if (elevatorHeight < TRANSIENT_MIN_SCROLLBAR_SIZE) 83 elevatorHeight = TRANSIENT_MIN_SCROLLBAR_SIZE; 84 85 if (offsetWidth >= scrollWidth) { 86 if (!el.scrollHorEl.classList.contains('hidden')) 87 el.scrollHorEl.classList.add('hidden'); 88 } else { 89 if (el.scrollHorEl.classList.contains('hidden')) 90 el.scrollHorEl.classList.remove('hidden'); 91 var x = scrollLeft / (scrollWidth - offsetWidth); 92 93 // TODO(mwichary): 6 shouldn’t be hardcoded 94 el.scrollHorEl.style.left = (x * (offsetWidth - elevatorWidth - 6)) + 'px'; 95 el.scrollHorEl.style.width = elevatorWidth + 'px'; 96 } 97 98 if (offsetHeight >= scrollHeight) { 99 if (!el.scrollVertEl.classList.contains('hidden')) 100 el.scrollVertEl.classList.add('hidden'); 101 } else { 102 if (el.scrollVertEl.classList.contains('hidden')) 103 el.scrollVertEl.classList.remove('hidden'); 104 105 var y = scrollTop / (scrollHeight - offsetHeight); 106 107 // TODO(mwichary): 6 shouldn’t be hardcoded 108 el.scrollVertEl.style.top = 109 (y * (offsetHeight - elevatorHeight - 6)) + 'px'; 110 el.scrollVertEl.style.height = elevatorHeight + 'px'; 111 } 112} 113 114/** 115 * Updates all exising scrollbars. 116 */ 117function updateAllTransientScrollbars() { 118 for (var i = 0; i < transientScrollbarEls.length; i++) { 119 var el = transientScrollbarEls[i]; 120 updateTransientScrollbars(el); 121 } 122} 123 124/** 125 * Handles a scroll event. 126 */ 127function handleTransientScroll(event) { 128 var el = event.target; 129 130 if (el == document) 131 el = document.body; 132 133 updateTransientScrollbars(el); 134 135 // Make sure to show the scrollbars if they are hidden. 136 showTransientScrollbars(el); 137} 138 139/** 140 * Adds scrollbars to the input element. 141 * 142 * @param {HTMLElement} scrollableEl The scrollable element 143 */ 144function addTransientScrollbars(scrollableEl) { 145 var insideEl = scrollableEl.querySelector('.scrollbar-inside'); 146 147 if (!insideEl) 148 insideEl = scrollableEl; 149 150 // Determine the width/height of native scrollbar elements. 151 insideEl.scrollbarWidth = insideEl.offsetWidth - insideEl.clientWidth; 152 insideEl.scrollbarHeight = insideEl.offsetHeight - insideEl.clientHeight; 153 154 // Create scrollbar elements 155 insideEl.scrollHorEl = document.createElement('div'); 156 insideEl.scrollHorEl.className = 'scrollbar hor'; 157 scrollableEl.appendChild(insideEl.scrollHorEl); 158 159 insideEl.scrollVertEl = document.createElement('div'); 160 insideEl.scrollVertEl.className = 'scrollbar vert'; 161 scrollableEl.appendChild(insideEl.scrollVertEl); 162 163 // Need to make sure the scrollbars are absolutely positioned vis-a-vis 164 // their parent element. If not, make its position relative. 165 if (insideEl.scrollVertEl.offsetParent != scrollableEl) 166 scrollableEl.style.position = 'relative'; 167 168 if (insideEl == document.body) 169 window.addEventListener('scroll', handleTransientScroll, false); 170 else 171 insideEl.addEventListener('scroll', handleTransientScroll, false); 172 insideEl.addEventListener('mousemove', handleTransientMouseMove, false); 173 174 updateTransientScrollbars(insideEl); 175 showTransientScrollbars(insideEl); 176 177 transientScrollbarEls.push(insideEl); 178} 179 180/** 181 * Creates webkit animation as specified by |code. 182 * 183 * @param {string} code The code specifying the animation. 184 */ 185function addAnimation(code) { 186 var name = 'anim' + animationCounter; 187 animationCounter++; 188 189 var rules = document.createTextNode( 190 '@-webkit-keyframes ' + name + ' {' + code + '}'); 191 192 var el = document.createElement('style'); 193 el.type = 'text/css'; 194 el.appendChild(rules); 195 document.body.appendChild(el); 196 197 return name; 198} 199 200/** 201 * Shows/hides elements of class "hidden-section" depending on their 202 * current state. 203 * 204 * @param {HTLMElement} el The element to be shown/hidden 205 */ 206function handleZippyClickEl(el) { 207 while (el.tagName != 'LI' && !el.classList.contains('hidden-section')) 208 el = el.parentNode; 209 210 var extraEl = el.querySelector('.extra'); 211 212 if (!el.classList.contains('opened')) { 213 extraEl.style.height = 'auto'; 214 var height = extraEl.offsetHeight; 215 216 extraEl.style.height = height + 'px'; 217 218 el.classList.add('opened'); 219 220 var animName = addAnimation( 221 '0% { opacity: 0; height: 0; padding-top: 0; } ' + 222 '80% { height: ' + (height + 2) + 'px; padding-top: 12px; }' + 223 '100% { opacity: 1; height: ' + height + 'px; padding-top: 10px; }'); 224 225 extraEl.style.webkitAnimationName = animName; 226 227 } else { 228 extraEl.style.webkitAnimationName = ''; 229 extraEl.style.height = 'auto'; 230 el.classList.remove('opened'); 231 el.classList.add('closing'); 232 233 // DEBUG 234 window.setTimeout( 235 function() { handleZippyClickCleanup(el, extraEl); }, 120); 236 } 237 238 window.setTimeout(updateAllTransientScrollbars, 100); 239} 240 241/** 242 * Cleans up the show/hide animation of function handleZippyClickEl(). 243 */ 244function handleZippyClickCleanup(el, extraEl) { 245 extraEl.style.height = ''; 246 el.classList.remove('closing'); 247} 248 249/** 250 * Handler for the load event. 251 */ 252function handleBodyLoad() { 253 document.body.classList.add('loaded'); 254} 255 256/** 257 * Initializes the scrollbars animation. 258 */ 259function initializeAnimation() { 260 if (document.querySelector('body > .sidebar')) 261 addTransientScrollbars(document.querySelector('body > .sidebar')); 262 else 263 addTransientScrollbars(document.body); 264 265 window.addEventListener('resize', updateAllTransientScrollbars, false); 266 window.addEventListener('load', handleBodyLoad, false); 267} 268