Index: chrome/browser/resources/touch_ntp/slider.js |
diff --git a/chrome/browser/resources/touch_ntp/slider.js b/chrome/browser/resources/touch_ntp/slider.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..ef0652fff921428850c9848ff76ec10a073c91b8 |
--- /dev/null |
+++ b/chrome/browser/resources/touch_ntp/slider.js |
@@ -0,0 +1,333 @@ |
+// 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. |
+ |
+/** |
+ * @fileoverview Card slider implementation. Allows you to create interactions |
+ * that have items that can slide left to right to reveal additional items. |
+ * Works by adding the necessary event handlers to a specific DOM structure |
+ * including a frame, container and cards. |
+ * - The frame defines the boundary of one item. Each card will be expanded to |
+ * fill the width of the frame. This element is also overflow hidden so that |
+ * the additional items left / right do not trigger horizontal scrolling. |
+ * - The container is what all the touch events are attached to. This element |
+ * will be expanded to be the width of all cards. |
+ * - The cards are the individual viewable items. There should be one card for |
+ * each item in the list. Only one card will be visible at a time. Two cards |
+ * will be visible while you are transitioning between cards. |
+ * |
+ * This class is designed to work well on any hardware-accelerated touch device. |
+ * It should still work on pre-hardware accelerated devices it just won't feel |
+ * very good. It should also work well with a mouse. |
+ */ |
+ |
+/** |
+ * @constructor |
+ * @param {!Element} frame The bounding rectangle that cards are visible in. |
+ * @param {!Element} container The surrounding element that will have event |
+ * listeners attached to it. |
+ * @param {!Array.<!Element>} cards The individual viewable cards. |
+ * @param {number} currentCard The index of the card that is currently visible. |
+ * @param {number} cardWidth The width of each card should have. |
+ */ |
+function Slider(frame, container, cards, currentCard, cardWidth) { |
+ /** |
+ * @type {!Element} |
+ * @private |
+ */ |
+ this.frame_ = frame; |
+ |
+ /** |
+ * @type {!Element} |
+ * @private |
+ */ |
+ this.container_ = container; |
+ |
+ /** |
+ * @type {!Array.<!Element>} |
+ * @private |
+ */ |
+ this.cards_ = cards; |
+ |
+ /** |
+ * @type {number} |
+ * @private |
+ */ |
+ this.currentCard_ = currentCard; |
+ |
+ /** |
+ * @type {number} |
+ * @private |
+ */ |
+ this.cardWidth_ = cardWidth; |
+ |
+ /** |
+ * @type {!TouchHandler} |
+ * @private |
+ */ |
+ this.touchHandler_ = new TouchHandler(this.container_); |
+} |
+ |
+ |
+/** |
+ * Events fired by the slider. |
+ * Events are fired at the container. |
+ */ |
+Slider.EventType = { |
+ // Fired when the user slides to another card. |
+ CARD_CHANGED: 'slider:card_changed' |
+}; |
+ |
+ |
+/** |
+ * The current left offset of the container relative to the frame. |
+ * @type {number} |
+ * @private |
+ */ |
+Slider.prototype.currentLeft_ = 0; |
+ |
+ |
+/** |
+ * The time to transition between cards when animating. Measured in ms. |
+ * @type {number} |
+ * @private |
+ */ |
+Slider.TRANSITION_TIME_ = 200; |
+ |
+ |
+/** |
+ * The minimum velocity required to transition cards if they did not drag past |
+ * the halfway point between cards. Measured in pixels / ms. |
+ * @type {number} |
+ * @private |
+ */ |
+Slider.TRANSITION_VELOCITY_THRESHOLD_ = 0.2; |
+ |
+/** |
+ * Initialize all elements and event handlers. Must call after construction and |
+ * before usage. |
+ */ |
+Slider.prototype.initialize = function() { |
+ assert( |
+ window.getComputedStyle(this.container_, null).display == '-webkit-box', |
+ 'Container should be display -webkit-box.'); |
+ assert( |
+ window.getComputedStyle(this.frame_, null).overflow == 'hidden', |
+ 'Frame should be overflow hidden.'); |
+ assert( |
+ window.getComputedStyle(this.container_, null).position == 'static', |
+ 'Container should be position static.'); |
+ for (var i = 0, card; card = this.cards_[i]; i++) { |
+ assert( |
+ window.getComputedStyle(card, null).position == 'static', |
+ 'Cards should be position static.'); |
+ } |
+ |
+ this.updateCardWidths_(); |
+ this.transformToCurrentCard_(); |
+ |
+ this.container_.addEventListener(TouchHandler.EventType.TOUCH_START, |
+ this.onTouchStart_.bind(this), false); |
+ this.container_.addEventListener(TouchHandler.EventType.DRAG_START, |
+ this.onDragStart_.bind(this), false); |
+ this.container_.addEventListener(TouchHandler.EventType.DRAG_MOVE, |
+ this.onDragMove_.bind(this), false); |
+ this.container_.addEventListener(TouchHandler.EventType.DRAG_END, |
+ this.onDragEnd_.bind(this), false); |
+ |
+ this.touchHandler_.enable(/* opt_capture */ false); |
+}; |
+ |
+ |
+/** |
+ * Use in cases where the width of the frame has changed in order to update the |
+ * width of cards. For example should be used when orientation changes in |
+ * full width sliders. |
+ * @param {number} newCardWidth Width all cards should have, in pixels. |
+ */ |
+Slider.prototype.resize = function(newCardWidth) { |
+ if (newCardWidth != this.cardWidth_) { |
+ this.cardWidth_ = newCardWidth; |
+ |
+ this.updateCardWidths_(); |
+ |
+ // Must upate the transform on the container to show the correct card. |
+ this.transformToCurrentCard_(); |
+ } |
+}; |
+ |
+ |
+/** |
+ * Sets the cards used. Can be called more than once to switch card sets. |
+ * @param {!Array.<!Element>} cards The individual viewable cards. |
+ * @param {number} index Index of the card to in the new set of cards to |
+ * navigate to. |
+ */ |
+Slider.prototype.setCards = function(cards, index) { |
+ assert(index >= 0 && index < cards.length, |
+ 'Invalid index in Slider#setCards'); |
+ this.cards_ = cards; |
+ |
+ this.updateCardWidths_(); |
+ |
+ // Jump to the given card index. |
+ this.setCurrentCard(index); |
+}; |
+ |
+ |
+/** |
+ * Updates the width of each card. |
+ * @private |
+ */ |
+Slider.prototype.updateCardWidths_ = function() { |
+ for (var i = 0, card; card = this.cards_[i]; i++) { |
+ card.style.width = this.cardWidth_ + 'px'; |
+ } |
+}; |
+ |
+ |
+/** |
+ * Returns the index of the current card. |
+ * @return {number} index of the current card. |
+ */ |
+Slider.prototype.getCurrentCard = function() { |
+ return this.currentCard_; |
+}; |
+ |
+/** |
+ * Clear any transition that is in progress and enable dragging for the touch. |
+ * @param {!CustomEvent} e The TouchHandler event. |
+ * @private |
+ */ |
+Slider.prototype.onTouchStart_ = function(e) { |
+ this.container_.style.WebkitTransition = ''; |
+ e.detail.enableDrag = true; |
+}; |
+ |
+ |
+/** |
+ * Tell the TouchHandler that dragging is acceptable when the user begins by |
+ * scrolling horizontally. |
+ * @param {!CustomEvent} e The TouchHandler event. |
+ * @private |
+ */ |
+Slider.prototype.onDragStart_ = function(e) { |
+ e.detail.enableDrag = Math.abs(e.detail.dragDeltaX) > |
+ Math.abs(e.detail.dragDeltaY); |
+}; |
+ |
+ |
+/** |
+ * On each drag move event reposition the container appropriately so the cards |
+ * look like they are sliding. |
+ * @param {!CustomEvent} e The TouchHandler event. |
+ * @private |
+ */ |
+Slider.prototype.onDragMove_ = function(e) { |
+ var deltaX = e.detail.dragDeltaX; |
+ // If dragging beyond the first or last card then apply a backoff so the |
+ // dragging feels stickier than usual. |
+ if (!this.currentCard_ && deltaX > 0 || |
+ this.currentCard_ == (this.cards_.length - 1) && deltaX < 0) { |
+ deltaX /= 2; |
+ } |
+ this.translateTo_(this.currentLeft_ + deltaX); |
+}; |
+ |
+/** |
+ * Moves the view to the specified position. |
+ * @param {number} x Horizontal position to move to. |
+ * @private |
+ */ |
+Slider.prototype.translateTo_ = function(x) { |
+ // We use a webkitTransform to slide because this is GPU accelerated on Chrome |
+ // and iOS. Once Chrome does GPU acceleration on the position fixed-layout |
+ // elements we could simply set the element's position to fixed and modify |
+ // 'left' instead. |
+ this.container_.style.WebkitTransform = 'translate3d(' + x + 'px, 0, 0)'; |
+}; |
+ |
+/** |
+ * On drag end events we may want to transition to another card, depending on |
+ * the ending position of the drag and the velocity of the drag. |
+ * @param {!CustomEvent} e The TouchHandler event. |
+ * @private |
+ */ |
+Slider.prototype.onDragEnd_ = function(e) { |
+ var deltaX = e.detail.dragDeltaX; |
+ var velocity = this.touchHandler_.getEndVelocity().x; |
+ var newX = this.currentLeft_ + deltaX; |
+ var newCardIndex = Math.round(-newX / this.cardWidth_); |
+ |
+ if (newCardIndex == this.currentCard_ && Math.abs(velocity) > |
+ Slider.TRANSITION_VELOCITY_THRESHOLD_) { |
+ // If the drag wasn't far enough to change cards but the velocity was high |
+ // enough to transition anyways. |
+ // If the velocity is to the left (negative) then the user wishes to go |
+ // right (card +1). |
+ newCardIndex += velocity > 0 ? -1 : 1; |
+ } |
+ |
+ this.setCurrentCard(newCardIndex, /* animate */ true); |
+}; |
+ |
+/** |
+ * Cancel any current touch/slide as if we saw a touch end |
+ */ |
+Slider.prototype.cancelTouch = function() { |
+ // Stop listening to any current touch |
+ this.touchHandler_.cancelTouch(); |
+ |
+ // Ensure we're at a card bounary |
+ this.transformToCurrentCard_(true); |
+}; |
+ |
+/** |
+ * Selects a new card, ensuring that it is a valid index, transforming the |
+ * view and possibly calling the change card callback. |
+ * @param {number} newCardIndex Index of card to show. |
+ * @param {boolean=} opt_animate If true will animate transition from current |
+ * position to new position. |
+ */ |
+Slider.prototype.setCurrentCard = |
+ function(newCardIndex, opt_animate) { |
+ var isChangingCard = newCardIndex >= 0 && newCardIndex < this.cards_.length && |
+ newCardIndex != this.currentCard_; |
+ if (isChangingCard) { |
+ // If we have a new card index and it is valid then update the left position |
+ // and current card index. |
+ this.currentCard_ = newCardIndex; |
+ } |
+ |
+ this.transformToCurrentCard_(opt_animate); |
+ |
+ if (isChangingCard) { |
+ var event = document.createEvent('HTMLEvents'); |
+ event.initEvent(Slider.EventType.CARD_CHANGED, true, true); |
+ event.sender = this; |
+ this.container_.dispatchEvent(event); |
+ } |
+}; |
+ |
+/** |
+ * Centers the view on the card denoted by this.currentCard_. Can either animate |
+ * to that card or snap to it. |
+ * @param {boolean=} opt_animate If true will animate transition from current |
+ * position to new position. |
+ * @private |
+ */ |
+Slider.prototype.transformToCurrentCard_ = function(opt_animate) { |
+ this.currentLeft_ = -this.currentCard_ * this.cardWidth_; |
+ |
+ // Animate to the current card, which will either transition if the current |
+ // card is new, or reset the existing card if we didn't drag enough to change |
+ // cards. |
+ var transition = ''; |
+ if (opt_animate) { |
+ transition = '-webkit-transform ' + Slider.TRANSITION_TIME_ + |
+ 'ms ease-in-out'; |
+ } |
+ this.container_.style.WebkitTransition = transition; |
+ this.translateTo_(this.currentLeft_); |
+}; |
+ |