| 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..27c21fcb8cbd7c213510931e892aa89d76c871ed
|
| --- /dev/null
|
| +++ b/chrome/browser/resources/touch_ntp/slider.js
|
| @@ -0,0 +1,327 @@
|
| +// 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() {
|
| + var view = this.container_.ownerDocument.defaultView;
|
| + assert(view.getComputedStyle(this.container_).display == '-webkit-box',
|
| + 'Container should be display -webkit-box.');
|
| + assert(view.getComputedStyle(this.frame_).overflow == 'hidden',
|
| + 'Frame should be overflow hidden.');
|
| + assert(view.getComputedStyle(this.container_).position == 'static',
|
| + 'Container should be position static.');
|
| + for (var i = 0, card; card = this.cards_[i]; i++)
|
| + assert(view.getComputedStyle(card).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_);
|
| +};
|
| +
|
|
|