// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // This file contains functions that control several animations in the print // preview tab (scrollbars, showing hiding options, resizing). // Scrollbar will never get smaller than this const TRANSIENT_MIN_SCROLLBAR_SIZE = 40; // Timeout duration in milliseconds used for showing the scrollbars. const HIDE_SCROLLBARS_TIMEOUT = 250; // Timeout id used to reset the timer when needed. var transientHideScrollbarsTimeoutId = []; // Holds all elements that have scrollbars attached to them. var transientScrollbarEls = []; // Counter used to give webkit animations unique names. var animationCounter = 0; /** * Makes the scrollbars visible. If |el| has already a scrollbar timer, it * resets its value. * * @param {HTMLDivElement} el The element associated with the scrollbars */ function showTransientScrollbars(el) { el.scrollHorEl.classList.remove('invisible'); el.scrollVertEl.classList.remove('invisible'); if (el.transientHideScrollbarsTimeoutId) window.clearTimeout(el.transientHideScrollbarsTimeoutId); el.transientHideScrollbarsTimeoutId = window.setTimeout(function() { hideTransientScrollbars(el) }, HIDE_SCROLLBARS_TIMEOUT); } /** * Hides the scrollbars. * * @param {HTMLElement} el The element associated with the scrollbars */ function hideTransientScrollbars(el) { el.scrollHorEl.classList.add('invisible'); el.scrollVertEl.classList.add('invisible'); } /** * Handles a mouse move event, takes care of updating the scrollbars. * * @param {event} event The event that triggered this handler */ function handleTransientMouseMove(event) { var el = event.target; while (!el.classList.contains('scrollbar-inside') && el != document.body) el = el.parentNode; showTransientScrollbars(el); } /** * Updates the scrollbars associated with the the input element. * * @param {HTMLElement} el */ function updateTransientScrollbars(el) { var scrollLeft = el.scrollLeft; var scrollTop = el.scrollTop; var scrollWidth = el.scrollWidth; var scrollHeight = el.scrollHeight; var offsetWidth = el.offsetWidth - el.scrollbarWidth; var offsetHeight = el.offsetHeight - el.scrollbarHeight; var elevatorWidth = offsetWidth / scrollWidth * offsetWidth; var elevatorHeight = offsetHeight / scrollHeight * offsetHeight; // Make sure the scrollbars are big enough. if (elevatorWidth < TRANSIENT_MIN_SCROLLBAR_SIZE) elevatorWidth = TRANSIENT_MIN_SCROLLBAR_SIZE; if (elevatorHeight < TRANSIENT_MIN_SCROLLBAR_SIZE) elevatorHeight = TRANSIENT_MIN_SCROLLBAR_SIZE; if (offsetWidth >= scrollWidth) { if (!el.scrollHorEl.classList.contains('hidden')) el.scrollHorEl.classList.add('hidden'); } else { if (el.scrollHorEl.classList.contains('hidden')) el.scrollHorEl.classList.remove('hidden'); var x = scrollLeft / (scrollWidth - offsetWidth); // TODO(mwichary): 6 shouldn’t be hardcoded el.scrollHorEl.style.left = (x * (offsetWidth - elevatorWidth - 6)) + 'px'; el.scrollHorEl.style.width = elevatorWidth + 'px'; } if (offsetHeight >= scrollHeight) { if (!el.scrollVertEl.classList.contains('hidden')) el.scrollVertEl.classList.add('hidden'); } else { if (el.scrollVertEl.classList.contains('hidden')) el.scrollVertEl.classList.remove('hidden'); var y = scrollTop / (scrollHeight - offsetHeight); // TODO(mwichary): 6 shouldn’t be hardcoded el.scrollVertEl.style.top = (y * (offsetHeight - elevatorHeight - 6)) + 'px'; el.scrollVertEl.style.height = elevatorHeight + 'px'; } } /** * Updates all exising scrollbars. */ function updateAllTransientScrollbars() { for (var i = 0; i < transientScrollbarEls.length; i++) { var el = transientScrollbarEls[i]; updateTransientScrollbars(el); } } /** * Handles a scroll event. */ function handleTransientScroll(event) { var el = event.target; if (el == document) el = document.body; updateTransientScrollbars(el); // Make sure to show the scrollbars if they are hidden. showTransientScrollbars(el); } /** * Adds scrollbars to the input element. * * @param {HTMLElement} scrollableEl The scrollable element */ function addTransientScrollbars(scrollableEl) { var insideEl = scrollableEl.querySelector('.scrollbar-inside'); if (!insideEl) insideEl = scrollableEl; // Determine the width/height of native scrollbar elements. insideEl.scrollbarWidth = insideEl.offsetWidth - insideEl.clientWidth; insideEl.scrollbarHeight = insideEl.offsetHeight - insideEl.clientHeight; // Create scrollbar elements insideEl.scrollHorEl = document.createElement('div'); insideEl.scrollHorEl.className = 'scrollbar hor'; scrollableEl.appendChild(insideEl.scrollHorEl); insideEl.scrollVertEl = document.createElement('div'); insideEl.scrollVertEl.className = 'scrollbar vert'; scrollableEl.appendChild(insideEl.scrollVertEl); // Need to make sure the scrollbars are absolutely positioned vis-a-vis // their parent element. If not, make its position relative. if (insideEl.scrollVertEl.offsetParent != scrollableEl) scrollableEl.style.position = 'relative'; if (insideEl == document.body) window.addEventListener('scroll', handleTransientScroll, false); else insideEl.addEventListener('scroll', handleTransientScroll, false); insideEl.addEventListener('mousemove', handleTransientMouseMove, false); updateTransientScrollbars(insideEl); showTransientScrollbars(insideEl); transientScrollbarEls.push(insideEl); } /** * Creates webkit animation as specified by |code. * * @param {string} code The code specifying the animation. */ function addAnimation(code) { var name = 'anim' + animationCounter; animationCounter++; var rules = document.createTextNode( '@-webkit-keyframes ' + name + ' {' + code + '}'); var el = document.createElement('style'); el.type = 'text/css'; el.appendChild(rules); document.body.appendChild(el); return name; } /** * Shows/hides elements of class "hidden-section" depending on their * current state. * * @param {HTLMElement} el The element to be shown/hidden */ function handleZippyClickEl(el) { while (el.tagName != 'LI' && !el.classList.contains('hidden-section')) el = el.parentNode; var extraEl = el.querySelector('.extra'); if (!el.classList.contains('opened')) { extraEl.style.height = 'auto'; var height = extraEl.offsetHeight; extraEl.style.height = height + 'px'; el.classList.add('opened'); var animName = addAnimation( '0% { opacity: 0; height: 0; padding-top: 0; } ' + '80% { height: ' + (height + 2) + 'px; padding-top: 12px; }' + '100% { opacity: 1; height: ' + height + 'px; padding-top: 10px; }'); extraEl.style.webkitAnimationName = animName; } else { extraEl.style.webkitAnimationName = ''; extraEl.style.height = 'auto'; el.classList.remove('opened'); el.classList.add('closing'); // DEBUG window.setTimeout( function() { handleZippyClickCleanup(el, extraEl); }, 120); } window.setTimeout(updateAllTransientScrollbars, 100); } /** * Cleans up the show/hide animation of function handleZippyClickEl(). */ function handleZippyClickCleanup(el, extraEl) { extraEl.style.height = ''; el.classList.remove('closing'); } /** * Handler for the load event. */ function handleBodyLoad() { document.body.classList.add('loaded'); } /** * Initializes the scrollbars animation. */ function initializeAnimation() { if (document.querySelector('body > .sidebar')) addTransientScrollbars(document.querySelector('body > .sidebar')); else addTransientScrollbars(document.body); window.addEventListener('resize', updateAllTransientScrollbars, false); window.addEventListener('load', handleBodyLoad, false); }