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

Side by Side Diff: chrome/browser/resources/settings/settings_page/main_page_behavior.js

Issue 2106013002: Move settings-section animations into setting-section, make better (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@Transitions
Patch Set: use strict Created 4 years, 5 months 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
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 // Fast out, slow in.
6 var EASING_FUNCTION = 'cubic-bezier(0.4, 0, 0.2, 1)';
7 var EXPAND_DURATION = 350;
8
9 /** 5 /**
10 * Calls |readyTest| repeatedly until it returns true, then calls 6 * Calls |readyTest| repeatedly until it returns true, then calls
11 * |readyCallback|. 7 * |readyCallback|.
12 * @param {function():boolean} readyTest 8 * @param {function():boolean} readyTest
13 * @param {!Function} readyCallback 9 * @param {!Function} readyCallback
14 */ 10 */
15 function doWhenReady(readyTest, readyCallback) { 11 function doWhenReady(readyTest, readyCallback) {
16 // TODO(dschuyler): Determine whether this hack can be removed. 12 // TODO(dschuyler): Determine whether this hack can be removed.
17 // See also: https://github.com/Polymer/polymer/issues/3629 13 // See also: https://github.com/Polymer/polymer/issues/3629
18 var intervalId = setInterval(function() { 14 var intervalId = setInterval(function() {
(...skipping 10 matching lines...) Expand all
29 * section should be expanded at any given time. 25 * section should be expanded at any given time.
30 * @polymerBehavior Polymer.MainPageBehavior 26 * @polymerBehavior Polymer.MainPageBehavior
31 */ 27 */
32 var MainPageBehaviorImpl = { 28 var MainPageBehaviorImpl = {
33 /** 29 /**
34 * @type {string} Selector to get the sections. Derived elements 30 * @type {string} Selector to get the sections. Derived elements
35 * must override. 31 * must override.
36 */ 32 */
37 sectionSelector: '', 33 sectionSelector: '',
38 34
39 /** @type {?Element} The scrolling container. */
40 scroller: null,
41
42 /** @override */ 35 /** @override */
43 attached: function() { 36 attached: function() {
37 /** @type {!HTMLElement} The scrolling container. */
44 this.scroller = this.domHost && this.domHost.parentNode.$.mainContainer; 38 this.scroller = this.domHost && this.domHost.parentNode.$.mainContainer;
45 }, 39 },
46 40
47 /** 41 /**
48 * Hides or unhides the sections not being expanded. 42 * Hides or unhides the sections not being expanded.
49 * @param {string} sectionName The section to keep visible. 43 * @param {string} sectionName The section to keep visible.
50 * @param {boolean} hidden Whether the sections should be hidden. 44 * @param {boolean} hidden Whether the sections should be hidden.
51 * @private 45 * @private
52 */ 46 */
53 toggleOtherSectionsHidden_: function(sectionName, hidden) { 47 toggleOtherSectionsHidden_: function(sectionName, hidden) {
54 var sections = Polymer.dom(this.root).querySelectorAll( 48 var sections = Polymer.dom(this.root).querySelectorAll(
55 this.sectionSelector + ':not([section=' + sectionName + '])'); 49 this.sectionSelector + ':not([section=' + sectionName + '])');
56 for (var section of sections) 50 for (var section of sections)
57 section.hidden = hidden; 51 section.hidden = hidden;
58 }, 52 },
59 53
60 /** 54 /**
61 * Animates the card in |section|, expanding it to fill the page. 55 * Animates the card in |section|, expanding it to fill the page.
62 * @param {!SettingsSectionElement} section 56 * @param {!SettingsSectionElement} section
63 */ 57 */
64 expandSection: function(section) { 58 expandSection: function(section) {
65 // If another section's card is expanding, cancel that animation first. 59 // TODO(michaelpg): Manage transition lifetime better.
66 var expanding = this.$$('.expanding'); 60 // crbug.com/624145
67 if (expanding) { 61 if (this.openSectionTransition_) {
68 if (expanding == section) 62 if (this.openSectionTransition_.section == section)
69 return; 63 return;
70 64
71 if (this.animations['section']) { 65 // Cancel the other section's opening transition.
72 // Cancel the animation, then call startExpandSection_. 66 this.openSectionTransition_.cancel();
73 this.cancelAnimation('section', function() { 67 this.openSectionTransition_.finished.then(function(success) {
74 this.startExpandSection_(section); 68 if (success) {
75 }.bind(this)); 69 // The other transition finished before we could cancel, so close that
76 } else { 70 // other section now.
77 // The animation must have finished but its promise hasn't resolved yet. 71 this.collapseSection(this.openSectionTransition_.section);
78 // When it resolves, collapse that section's card before expanding 72 }
79 // this one. 73 // Call the outer function again to actually open the section.
80 setTimeout(function() { 74 this.expandSection(section);
81 this.collapseSection(
82 /** @type {!SettingsSectionElement} */(expanding));
83 this.finishAnimation('section', function() {
84 this.startExpandSection_(section);
85 }.bind(this));
86 }.bind(this));
87 }
88
89 return;
90 }
91
92 if (this.$$('.collapsing') && this.animations['section']) {
93 // Finish the collapse animation before expanding.
94 this.finishAnimation('section', function() {
95 this.startExpandSection_(section);
96 }.bind(this)); 75 }.bind(this));
97 return; 76 return;
98 } 77 }
99 78
100 this.startExpandSection_(section); 79 if (this.closeSectionTransition_) {
101 }, 80 // Finish collapsing the section, then expand this section.
81 this.closeSectionTransition_.finished.then(function(success) {
82 this.expandSection(section);
83 }.bind(this));
84 return;
85 }
102 86
103 /** 87 this.openSectionTransition_ =
104 * Helper function to set up and start the expand animation. 88 new settings.OpenSectionTransition(section, this.scroller);
105 * @param {!SettingsSectionElement} section
106 */
107 startExpandSection_: function(section) {
108 if (section.classList.contains('expanded'))
109 return;
110 89
111 // Freeze the scroller and save its position. 90 // Freeze the scroller and save its position.
112 this.listScrollTop_ = this.scroller.scrollTop; 91 this.scroller.listScrollTop_ = this.scroller.scrollTop;
113
114 var scrollerWidth = this.scroller.clientWidth; 92 var scrollerWidth = this.scroller.clientWidth;
115 this.scroller.style.overflow = 'hidden'; 93 this.scroller.style.overflow = 'hidden';
94
116 // Adjust width to compensate for scroller. 95 // Adjust width to compensate for scroller.
117 var scrollbarWidth = this.scroller.clientWidth - scrollerWidth; 96 var scrollbarWidth = this.scroller.clientWidth - scrollerWidth;
118 this.scroller.style.width = 'calc(100% - ' + scrollbarWidth + 'px)'; 97 this.scroller.style.width = 'calc(100% - ' + scrollbarWidth + 'px)';
119 98
120 // Freezes the section's height so its card can be removed from the flow. 99 this.openSectionTransition_.play()
121 this.freezeSection_(section); 100 .then(function(success) {
101 if (success) {
102 this.toggleOtherSectionsHidden_(section.section, true);
103 this.scroller.scrollTop = 0;
104 this.classList.add('showing-subpage');
105 this.fire('subpage-expand');
106 } else {
107 this.scroller.scrollTop = this.scroller.listScrollTop_;
108 }
122 109
123 // Expand the section's card to fill the parent. 110 this.scroller.style.width = '';
124 var animationPromise = this.playExpandSection_(section); 111 this.scroller.style.overflow = '';
125 112 this.openSectionTransition_ = null;
126 animationPromise.then(function() { 113 }.bind(this));
127 this.scroller.scrollTop = 0;
128 this.toggleOtherSectionsHidden_(section.section, true);
129 }.bind(this), function() {
130 // Animation was canceled; restore the section.
131 this.unfreezeSection_(section);
132 }.bind(this)).then(function() {
133 this.scroller.style.overflow = '';
134 this.scroller.style.width = '';
135 }.bind(this));
136 }, 114 },
137 115
138 /** 116 /**
139 * Animates the card in |section|, collapsing it back into its section. 117 * Animates the card in |section|, collapsing it back into its section.
140 * @param {!SettingsSectionElement} section 118 * @param {!SettingsSectionElement} section
141 */ 119 */
142 collapseSection: function(section) { 120 collapseSection: function(section) {
143 // If the section's card is still expanding, cancel the expand animation. 121 if (this.closeSectionTransition_) {
144 if (section.classList.contains('expanding')) { 122 assert(this.closeSectionTransition_.section == section);
Dan Beam 2016/07/23 01:13:44 i don't really understand why this assert() must b
145 if (this.animations['section']) { 123 this.closeSectionTransition_.cancel();
146 this.cancelAnimation('section');
147 } else {
148 // The animation must have finished but its promise hasn't finished
149 // resolving; try again asynchronously.
150 this.async(function() {
151 this.collapseSection(section);
152 });
153 }
154 return; 124 return;
155 } 125 }
156 126
157 if (!section.classList.contains('expanded')) 127 if (this.openSectionTransition_) {
128 assert(this.openSectionTransition_.section == section);
129 this.openSectionTransition_.cancel();
158 return; 130 return;
131 }
159 132
160 this.toggleOtherSectionsHidden_(section.section, false); 133 assert(section.classList.contains('expanded'));
161 134
162 var scrollerWidth = this.scroller.clientWidth; 135 var scrollerWidth = this.scroller.clientWidth;
163 this.scroller.style.overflow = 'hidden'; 136 this.scroller.style.overflow = 'hidden';
137
164 // Adjust width to compensate for scroller. 138 // Adjust width to compensate for scroller.
139 // TODO(michaelpg): Minimize horizontal motion when scrollbar changes for
140 // the common cases.
165 var scrollbarWidth = this.scroller.clientWidth - scrollerWidth; 141 var scrollbarWidth = this.scroller.clientWidth - scrollerWidth;
166 this.scroller.style.width = 'calc(100% - ' + scrollbarWidth + 'px)'; 142 this.scroller.style.width = 'calc(100% - ' + scrollbarWidth + 'px)';
167 143
168 this.playCollapseSection_(section).then(function() { 144 // Allow time for the dom-ifs in settings-main to re-render.
169 this.unfreezeSection_(section); 145 // TODO(michaelpg): Use a readiness signal (e.g., from the router) rather
170 this.scroller.style.overflow = ''; 146 // than firing events for settings-main and running this function async.
171 this.scroller.style.width = ''; 147 this.fire('subpage-collapsing');
172 section.classList.remove('collapsing'); 148 this.async(function() {
173 }.bind(this)); 149 // Set up the close transition first to take the section out of the flow
174 }, 150 // before showing everything.
151 this.closeSectionTransition_ =
152 new settings.CloseSectionTransition(section, this.scroller);
153 this.closeSectionTransition_.setUp();
175 154
176 /** 155 this.toggleOtherSectionsHidden_(section.section, false);
177 * Freezes a section's height so its card can be removed from the flow without 156 this.classList.remove('showing-subpage');
178 * affecting the layout of the surrounding sections. 157 this.scroller.scrollTop = this.scroller.listScrollTop_;
179 * @param {!SettingsSectionElement} section
180 * @private
181 */
182 freezeSection_: function(section) {
183 var card = section.$.card;
184 section.style.height = section.clientHeight + 'px';
185 158
186 var cardHeight = card.offsetHeight; 159 this.closeSectionTransition_.play().then(function(success) {
187 var cardWidth = card.offsetWidth; 160 if (!success)
188 // If the section is not displayed yet (e.g., navigated directly to a 161 this.fire('subpage-expand');
189 // sub-page), cardHeight and cardWidth are 0, so do not set the height or
190 // width explicitly.
191 // TODO(michaelpg): Improve this logic when refactoring
192 // settings-animated-pages.
193 if (cardHeight && cardWidth) {
194 // TODO(michaelpg): Temporary hack to store the height the section should
195 // collapse to when it closes.
196 card.origHeight_ = cardHeight;
197 162
198 card.style.height = cardHeight + 'px'; 163 this.scroller.style.overflow = '';
199 card.style.width = cardWidth + 'px'; 164 this.scroller.style.width = '';
200 } else { 165 this.closeSectionTransition_ = null;
201 // Set an invalid value so we don't try to use it later. 166 }.bind(this));
202 card.origHeight_ = NaN;
203 }
204
205 // Place the section's card at its current position but removed from the
206 // flow.
207 card.style.top = card.getBoundingClientRect().top + 'px';
208 section.classList.add('frozen');
209 },
210
211 /**
212 * After freezeSection_, restores the section to its normal height.
213 * @param {!SettingsSectionElement} section
214 * @private
215 */
216 unfreezeSection_: function(section) {
217 if (!section.classList.contains('frozen'))
218 return;
219 var card = section.$.card;
220 section.classList.remove('frozen');
221 card.style.top = '';
222 card.style.height = '';
223 card.style.width = '';
224 section.style.height = '';
225 },
226
227 /**
228 * Expands the card in |section| to fill the page.
229 * @param {!SettingsSectionElement} section
230 * @return {!Promise}
231 * @private
232 */
233 playExpandSection_: function(section) {
234 var card = section.$.card;
235
236 // The card should start at the top of the page.
237 var targetTop = this.scroller.getBoundingClientRect().top;
238
239 section.classList.add('expanding');
240
241 // Expand the card, using minHeight. (The card must span the container's
242 // client height, so it must be at least 100% in case the card is too short.
243 // If the card is already taller than the container's client height, we
244 // don't want to shrink the card to 100% or the content will overflow, so
245 // we can't use height, and animating height wouldn't look right anyway.)
246 var keyframes = [{
247 top: card.style.top,
248 minHeight: card.style.height,
249 easing: EASING_FUNCTION,
250 }, {
251 top: targetTop + 'px',
252 minHeight: 'calc(100% - ' + targetTop + 'px)',
253 }];
254 var options = /** @type {!KeyframeEffectOptions} */({
255 duration: EXPAND_DURATION
256 }); 167 });
257 // TODO(michaelpg): Change elevation of sections.
258 var promise;
259 if (keyframes[0].top && keyframes[0].minHeight)
260 promise = this.animateElement('section', card, keyframes, options);
261 else
262 promise = Promise.resolve();
263
264 promise.then(function() {
265 section.classList.add('expanded');
266 card.style.top = '';
267 this.style.margin = 'auto';
268 section.$.header.hidden = true;
269 section.style.height = '';
270 }.bind(this), function() {
271 // The animation was canceled; catch the error and continue.
272 }).then(function() {
273 // Whether finished or canceled, clean up the animation.
274 section.classList.remove('expanding');
275 card.style.height = '';
276 card.style.width = '';
277 });
278
279 return promise;
280 },
281
282 /**
283 * Collapses the card in |section| back to its normal position.
284 * @param {!SettingsSectionElement} section
285 * @return {!Promise}
286 * @private
287 */
288 playCollapseSection_: function(section) {
289 var card = section.$.card;
290
291 this.style.margin = '';
292 section.$.header.hidden = false;
293
294 var startingTop = this.scroller.getBoundingClientRect().top;
295
296 var cardHeightStart = card.clientHeight;
297 var cardWidthStart = card.clientWidth;
298
299 section.classList.add('collapsing');
300 section.classList.remove('expanding', 'expanded');
301
302 // If we navigated here directly, we don't know the original height of the
303 // section, so we skip the animation.
304 // TODO(michaelpg): remove this condition once sliding is implemented.
305 if (isNaN(card.origHeight_))
306 return Promise.resolve();
307
308 // Restore the section to its proper height to make room for the card.
309 section.style.height = section.clientHeight + card.origHeight_ + 'px';
310
311 // TODO(michaelpg): this should be in collapseSection(), but we need to wait
312 // until the full page height is available (setting the section height).
313 this.scroller.scrollTop = this.listScrollTop_;
314
315 // The card is unpositioned, so use its position as the ending state,
316 // but account for scroll.
317 var targetTop = card.getBoundingClientRect().top - this.scroller.scrollTop;
318
319 // Account for the section header.
320 var headerStyle = getComputedStyle(section.$.header);
321 targetTop += section.$.header.offsetHeight +
322 parseInt(headerStyle.marginBottom, 10) +
323 parseInt(headerStyle.marginTop, 10);
324
325 var keyframes = [{
326 top: startingTop + 'px',
327 minHeight: cardHeightStart + 'px',
328 easing: EASING_FUNCTION,
329 }, {
330 top: targetTop + 'px',
331 minHeight: card.origHeight_ + 'px',
332 }];
333 var options = /** @type {!KeyframeEffectOptions} */({
334 duration: EXPAND_DURATION
335 });
336
337 card.style.width = cardWidthStart + 'px';
338 var promise = this.animateElement('section', card, keyframes, options);
339 promise.then(function() {
340 card.style.width = '';
341 });
342 return promise;
343 }, 168 },
344 }; 169 };
345 170
346 171
347 /** @polymerBehavior */ 172 /** @polymerBehavior */
348 var MainPageBehavior = [ 173 var MainPageBehavior = [
349 TransitionBehavior,
350 MainPageBehaviorImpl 174 MainPageBehaviorImpl
351 ]; 175 ];
352 176
353 177
354 /** 178 /**
355 * TODO(michaelpg): integrate slide animations. 179 * TODO(michaelpg): integrate slide animations.
356 * @polymerBehavior RoutableBehavior 180 * @polymerBehavior RoutableBehavior
357 */ 181 */
358 var RoutableBehaviorImpl = { 182 var RoutableBehaviorImpl = {
359 properties: { 183 properties: {
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after
425 this.$$('[section=' + section + ']')); 249 this.$$('[section=' + section + ']'));
426 }, 250 },
427 }; 251 };
428 252
429 253
430 /** @polymerBehavior */ 254 /** @polymerBehavior */
431 var RoutableBehavior = [ 255 var RoutableBehavior = [
432 MainPageBehavior, 256 MainPageBehavior,
433 RoutableBehaviorImpl 257 RoutableBehaviorImpl
434 ]; 258 ];
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698