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

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

Powered by Google App Engine
This is Rietveld 408576698