Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(37)

Side by Side Diff: chrome/browser/resources/ntp4/card_slider.js

Issue 8423055: [Aura] Initial app list webui. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: remove grabber.js Created 9 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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 /**
6 * @fileoverview Card slider implementation. Allows you to create interactions
7 * that have items that can slide left to right to reveal additional items.
8 * Works by adding the necessary event handlers to a specific DOM structure
9 * including a frame, container and cards.
10 * - The frame defines the boundary of one item. Each card will be expanded to
11 * fill the width of the frame. This element is also overflow hidden so that
12 * the additional items left / right do not trigger horizontal scrolling.
13 * - The container is what all the touch events are attached to. This element
14 * will be expanded to be the width of all cards.
15 * - The cards are the individual viewable items. There should be one card for
16 * each item in the list. Only one card will be visible at a time. Two cards
17 * will be visible while you are transitioning between cards.
18 *
19 * This class is designed to work well on any hardware-accelerated touch device.
20 * It should still work on pre-hardware accelerated devices it just won't feel
21 * very good. It should also work well with a mouse.
22 */
23
24
25 // Use an anonymous function to enable strict mode just for this file (which
26 // will be concatenated with other files when embedded in Chrome
27 var CardSlider = (function() {
28 'use strict';
29
30 /**
31 * @constructor
32 * @param {!Element} frame The bounding rectangle that cards are visible in.
33 * @param {!Element} container The surrounding element that will have event
34 * listeners attached to it.
35 * @param {number} cardWidth The width of each card should have.
36 */
37 function CardSlider(frame, container, cardWidth) {
38 /**
39 * @type {!Element}
40 * @private
41 */
42 this.frame_ = frame;
43
44 /**
45 * @type {!Element}
46 * @private
47 */
48 this.container_ = container;
49
50 /**
51 * Array of card elements.
52 * @type {!Array.<!Element>}
53 * @private
54 */
55 this.cards_ = [];
56
57 /**
58 * Index of currently shown card.
59 * @type {number}
60 * @private
61 */
62 this.currentCard_ = 0;
63
64 /**
65 * @type {number}
66 * @private
67 */
68 this.cardWidth_ = cardWidth;
69
70 /**
71 * @type {!TouchHandler}
72 * @private
73 */
74 this.touchHandler_ = new TouchHandler(this.container_);
75
76 }
77
78 /**
79 * Events fired by the slider.
80 * Events are fired at the container.
81 */
82 CardSlider.EventType = {
83 // Fired when the user slides to another card.
84 CARD_CHANGED: 'cardSlider:card_changed'
85 };
86
87
88 /**
89 * The time to transition between cards when animating. Measured in ms.
90 * @type {number}
91 * @private
92 * @const
93 */
94 CardSlider.TRANSITION_TIME_ = 200;
95
96
97 /**
98 * The minimum velocity required to transition cards if they did not drag past
99 * the halfway point between cards. Measured in pixels / ms.
100 * @type {number}
101 * @private
102 * @const
103 */
104 CardSlider.TRANSITION_VELOCITY_THRESHOLD_ = 0.2;
105
106
107 CardSlider.prototype = {
108 /**
109 * The current left offset of the container relative to the frame.
110 * @type {number}
111 * @private
112 */
113 currentLeft_: 0,
114
115 /**
116 * Initialize all elements and event handlers. Must call after construction
117 * and before usage.
118 */
119 initialize: function() {
120 var view = this.container_.ownerDocument.defaultView;
121 assert(view.getComputedStyle(this.container_).display == '-webkit-box',
122 'Container should be display -webkit-box.');
123 assert(view.getComputedStyle(this.frame_).overflow == 'hidden',
124 'Frame should be overflow hidden.');
125 assert(view.getComputedStyle(this.container_).position == 'static',
126 'Container should be position static.');
127
128 this.updateCardWidths_();
129
130 this.mouseWheelScrollAmount_ = 0;
131 this.mouseWheelCardSelected_ = false;
132 this.mouseWheelIsContinuous_ = false;
133 this.scrollClearTimeout_ = null;
134 this.frame_.addEventListener('mousewheel',
135 this.onMouseWheel_.bind(this));
136
137 if (document.documentElement.getAttribute('touchui')) {
138 this.container_.addEventListener(TouchHandler.EventType.TOUCH_START,
139 this.onTouchStart_.bind(this));
140 this.container_.addEventListener(TouchHandler.EventType.DRAG_START,
141 this.onDragStart_.bind(this));
142 this.container_.addEventListener(TouchHandler.EventType.DRAG_MOVE,
143 this.onDragMove_.bind(this));
144 this.container_.addEventListener(TouchHandler.EventType.DRAG_END,
145 this.onDragEnd_.bind(this));
146
147 this.touchHandler_.enable(/* opt_capture */ false);
148 }
149 },
150
151 /**
152 * Use in cases where the width of the frame has changed in order to update
153 * the width of cards. For example should be used when orientation changes
154 * in full width sliders.
155 * @param {number} newCardWidth Width all cards should have, in pixels.
156 */
157 resize: function(newCardWidth) {
158 if (newCardWidth != this.cardWidth_) {
159 this.cardWidth_ = newCardWidth;
160
161 this.updateCardWidths_();
162
163 // Must upate the transform on the container to show the correct card.
164 this.transformToCurrentCard_();
165 }
166 },
167
168 /**
169 * Sets the cards used. Can be called more than once to switch card sets.
170 * @param {!Array.<!Element>} cards The individual viewable cards.
171 * @param {number} index Index of the card to in the new set of cards to
172 * navigate to.
173 */
174 setCards: function(cards, index) {
175 assert(index >= 0 && index < cards.length,
176 'Invalid index in CardSlider#setCards');
177 this.cards_ = cards;
178
179 this.updateCardWidths_();
180
181 // Jump to the given card index.
182 this.selectCard(index);
183 },
184
185 /**
186 * Updates the width of each card.
187 * @private
188 */
189 updateCardWidths_: function() {
190 for (var i = 0, card; card = this.cards_[i]; i++)
191 card.style.width = this.cardWidth_ + 'px';
192 },
193
194 /**
195 * Returns the index of the current card.
196 * @return {number} index of the current card.
197 */
198 get currentCard() {
199 return this.currentCard_;
200 },
201
202 /**
203 * Allows setting the current card index.
204 * @param {number} index A new index to set the current index to.
205 * @return {number} The new index after having been set.
206 */
207 set currentCard(index) {
208 return (this.currentCard_ = index);
209 },
210
211 /**
212 * Returns the number of cards.
213 * @return {number} number of cards.
214 */
215 get cardCount() {
216 return this.cards_.length;
217 },
218
219 /**
220 * Returns the current card itself.
221 * @return {!Element} the currently shown card.
222 */
223 get currentCardValue() {
224 return this.cards_[this.currentCard_];
225 },
226
227 /**
228 * Handle horizontal scrolls to flip between pages.
229 * @private
230 */
231 onMouseWheel_: function(e) {
232 if (e.wheelDeltaX == 0)
233 return;
234
235 // Prevent OS X 10.7+ history swiping on the NTP.
236 e.preventDefault();
237
238 // Continuous devices such as an Apple Touchpad or Apple MagicMouse will
239 // send arbitrary delta values. Conversly, standard mousewheels will
240 // send delta values in increments of 120. (There is of course a small
241 // chance we mistake a continuous device for a non-continuous device.
242 // Unfortunately there isn't a better way to do this until real touch
243 // events are available to desktop clients.)
244 var DISCRETE_DELTA = 120;
245 if (e.wheelDeltaX % DISCRETE_DELTA)
246 this.mouseWheelIsContinuous_ = true;
247
248 if (this.mouseWheelIsContinuous_) {
249 // For continuous devices, detect a page swipe when the accumulated
250 // delta matches a pre-defined threshhold. After changing the page,
251 // ignore wheel events for a short time before repeating this process.
252 if (this.mouseWheelCardSelected_) return;
253 this.mouseWheelScrollAmount_ += e.wheelDeltaX;
254 if (Math.abs(this.mouseWheelScrollAmount_) >= 600) {
255 var pagesToScroll = this.mouseWheelScrollAmount_ > 0 ? 1 : -1;
256 if (!ntp4.isRTL())
257 pagesToScroll *= -1;
258 var newCardIndex = this.currentCard + pagesToScroll;
259 newCardIndex = Math.min(this.cards_.length - 1,
260 Math.max(0, newCardIndex));
261 this.selectCard(newCardIndex, true);
262 this.mouseWheelCardSelected_ = true;
263 }
264 } else {
265 // For discrete devices, consider each wheel tick a page change.
266 var pagesToScroll = e.wheelDeltaX / DISCRETE_DELTA;
267 if (!ntp4.isRTL())
268 pagesToScroll *= -1;
269 var newCardIndex = this.currentCard + pagesToScroll;
270 newCardIndex = Math.min(this.cards_.length - 1,
271 Math.max(0, newCardIndex));
272 this.selectCard(newCardIndex, true);
273 }
274
275 // We got a mouse wheel event, so cancel any pending scroll wheel timeout.
276 if (this.scrollClearTimeout_ != null)
277 clearTimeout(this.scrollClearTimeout_);
278 // If we didn't use up all the scroll, hold onto it for a little bit, but
279 // drop it after a delay.
280 if (this.mouseWheelScrollAmount_ != 0) {
281 this.scrollClearTimeout_ =
282 setTimeout(this.clearMouseWheelScroll_.bind(this), 500);
283 }
284 },
285
286 /**
287 * Resets the amount of horizontal scroll we've seen to 0. See
288 * onMouseWheel_.
289 * @private
290 */
291 clearMouseWheelScroll_: function() {
292 this.mouseWheelScrollAmount_ = 0;
293 this.mouseWheelCardSelected_ = false;
294 },
295
296 /**
297 * Selects a new card, ensuring that it is a valid index, transforming the
298 * view and possibly calling the change card callback.
299 * @param {number} newCardIndex Index of card to show.
300 * @param {boolean=} opt_animate If true will animate transition from
301 * current position to new position.
302 */
303 selectCard: function(newCardIndex, opt_animate) {
304 var previousCard = this.currentCardValue;
305
306 var isChangingCard =
307 !this.cards_[newCardIndex].classList.contains('selected-card');
308
309 if (isChangingCard) {
310 previousCard.classList.remove('selected-card');
311 // If we have a new card index and it is valid then update the left
312 // position and current card index.
313 this.currentCard_ = newCardIndex;
314 this.currentCardValue.classList.add('selected-card');
315 }
316
317 this.transformToCurrentCard_(opt_animate);
318
319 if (isChangingCard) {
320 var event = document.createEvent('Event');
321 event.initEvent(CardSlider.EventType.CARD_CHANGED, true, true);
322 event.cardSlider = this;
323 this.container_.dispatchEvent(event);
324
325 // We also dispatch an event on the cards themselves.
326 if (previousCard) {
327 cr.dispatchSimpleEvent(previousCard, 'carddeselected',
328 true, true);
329 }
330 cr.dispatchSimpleEvent(this.currentCardValue, 'cardselected',
331 true, true);
332 }
333 },
334
335 /**
336 * Selects a card from the stack. Passes through to selectCard.
337 * @param {Node} newCard The card that should be selected.
338 * @param {boolean=} opt_animate Whether to animate.
339 */
340 selectCardByValue: function(newCard, opt_animate) {
341 var i = this.cards_.indexOf(newCard);
342 assert(i != -1);
343 this.selectCard(i, opt_animate);
344 },
345
346 /**
347 * Centers the view on the card denoted by this.currentCard. Can either
348 * animate to that card or snap to it.
349 * @param {boolean=} opt_animate If true will animate transition from
350 * current position to new position.
351 * @private
352 */
353 transformToCurrentCard_: function(opt_animate) {
354 this.currentLeft_ = -this.cardWidth_ *
355 (ntp4.isRTL() ? this.cards_.length - this.currentCard - 1 :
356 this.currentCard);
357
358 // Animate to the current card, which will either transition if the
359 // current card is new, or reset the existing card if we didn't drag
360 // enough to change cards.
361 var transition = '';
362 if (opt_animate) {
363 transition = '-webkit-transform ' + CardSlider.TRANSITION_TIME_ +
364 'ms ease-in-out';
365 }
366 this.container_.style.WebkitTransition = transition;
367 this.translateTo_(this.currentLeft_);
368 },
369
370 /**
371 * Moves the view to the specified position.
372 * @param {number} x Horizontal position to move to.
373 * @private
374 */
375 translateTo_: function(x) {
376 // We use a webkitTransform to slide because this is GPU accelerated on
377 // Chrome and iOS. Once Chrome does GPU acceleration on the position
378 // fixed-layout elements we could simply set the element's position to
379 // fixed and modify 'left' instead.
380 this.container_.style.WebkitTransform = 'translate3d(' + x + 'px, 0, 0)';
381 },
382
383 /* Touch ******************************************************************/
384
385 /**
386 * Clear any transition that is in progress and enable dragging for the
387 * touch.
388 * @param {!TouchHandler.Event} e The TouchHandler event.
389 * @private
390 */
391 onTouchStart_: function(e) {
392 this.container_.style.WebkitTransition = '';
393 e.enableDrag = true;
394 },
395
396 /**
397 * Tell the TouchHandler that dragging is acceptable when the user begins by
398 * scrolling horizontally.
399 * @param {!TouchHandler.Event} e The TouchHandler event.
400 * @private
401 */
402 onDragStart_: function(e) {
403 e.enableDrag = Math.abs(e.dragDeltaX) > Math.abs(e.dragDeltaY);
404 },
405
406 /**
407 * On each drag move event reposition the container appropriately so the
408 * cards look like they are sliding.
409 * @param {!TouchHandler.Event} e The TouchHandler event.
410 * @private
411 */
412 onDragMove_: function(e) {
413 var deltaX = e.dragDeltaX;
414 // If dragging beyond the first or last card then apply a backoff so the
415 // dragging feels stickier than usual.
416 if (!this.currentCard && deltaX > 0 ||
417 this.currentCard == (this.cards_.length - 1) && deltaX < 0) {
418 deltaX /= 2;
419 }
420 this.translateTo_(this.currentLeft_ + deltaX);
421 },
422
423 /**
424 * On drag end events we may want to transition to another card, depending
425 * on the ending position of the drag and the velocity of the drag.
426 * @param {!TouchHandler.Event} e The TouchHandler event.
427 * @private
428 */
429 onDragEnd_: function(e) {
430 var deltaX = e.dragDeltaX;
431 var velocity = this.touchHandler_.getEndVelocity().x;
432 var newX = this.currentLeft_ + deltaX;
433 var newCardIndex = Math.round(-newX / this.cardWidth_);
434
435 if (newCardIndex == this.currentCard && Math.abs(velocity) >
436 CardSlider.TRANSITION_VELOCITY_THRESHOLD_) {
437 // If the drag wasn't far enough to change cards but the velocity was
438 // high enough to transition anyways. If the velocity is to the left
439 // (negative) then the user wishes to go right (card +1).
440 newCardIndex += velocity > 0 ? -1 : 1;
441 }
442
443 this.selectCard(newCardIndex, /* animate */ true);
444 },
445
446 /**
447 * Cancel any current touch/slide as if we saw a touch end
448 */
449 cancelTouch: function() {
450 // Stop listening to any current touch
451 this.touchHandler_.cancelTouch();
452
453 // Ensure we're at a card bounary
454 this.transformToCurrentCard_(true);
455 },
456 };
457
458 return CardSlider;
459 })();
OLDNEW
« no previous file with comments | « chrome/browser/resources/aura/app_list/apps_view.js ('k') | chrome/browser/resources/ntp4/dot_list.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698