Home | History | Annotate | Download | only in resources
      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
      9 const TRANSIENT_MIN_SCROLLBAR_SIZE = 40;
     10 //  Timeout duration in milliseconds used for showing the scrollbars.
     11 const HIDE_SCROLLBARS_TIMEOUT = 250;
     12 // Timeout id used to reset the timer when needed.
     13 var transientHideScrollbarsTimeoutId = [];
     14 //  Holds all elements that have scrollbars attached to them.
     15 var transientScrollbarEls = [];
     16 // Counter used to give webkit animations unique names.
     17 var 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  */
     25 function 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 */
     42 function 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 */
     52 function 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  */
     66 function 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 shouldnt 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 shouldnt 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  */
    117 function 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  */
    127 function 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  */
    144 function 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  */
    185 function 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  */
    206 function 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  */
    244 function handleZippyClickCleanup(el, extraEl) {
    245   extraEl.style.height = '';
    246   el.classList.remove('closing');
    247 }
    248 
    249 /**
    250  * Handler for the load event.
    251  */
    252 function handleBodyLoad() {
    253   document.body.classList.add('loaded');
    254 }
    255 
    256 /**
    257  * Initializes the scrollbars animation.
    258  */
    259 function 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