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

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

Issue 2230123002: MD Settings: fix collapse animation once and for all (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@Overscroll2
Patch Set: rebase, rebase fix (doWhenReady) Created 4 years, 4 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 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 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 /** 5 /**
6 * @fileoverview 6 * @fileoverview
7 * 'settings-section' shows a paper material themed section with a header 7 * 'settings-section' shows a paper material themed section with a header
8 * which shows its page title. 8 * which shows its page title.
9 * 9 *
10 * The section can expand vertically to fill its container's padding edge. 10 * The section can expand vertically to fill its container's padding edge.
11 * 11 *
12 * Example: 12 * Example:
13 * 13 *
14 * <settings-section page-title="[[pageTitle]]" section="privacy"> 14 * <settings-section page-title="[[pageTitle]]" section="privacy">
15 * <!-- Insert your section controls here --> 15 * <!-- Insert your section controls here -->
16 * </settings-section> 16 * </settings-section>
17 */ 17 */
18 18
19 // Fast out, slow in.
20 var EASING_FUNCTION = 'cubic-bezier(0.4, 0, 0.2, 1)';
21 var EXPAND_DURATION = 350;
22
23 var SettingsSectionElement = Polymer({ 19 var SettingsSectionElement = Polymer({
24 is: 'settings-section', 20 is: 'settings-section',
25 21
26 properties: { 22 properties: {
27 /** 23 /**
28 * The section name should match a name specified in route.js. The 24 * The section name should match a name specified in route.js. The
29 * MainPageBehavior will expand this section if this section name matches 25 * MainPageBehavior will expand this section if this section name matches
30 * currentRoute.section. 26 * currentRoute.section.
31 */ 27 */
32 section: String, 28 section: String,
33 29
34 /** Title for the section header. */ 30 /** Title for the section header. */
35 pageTitle: String, 31 pageTitle: String,
36 32
37 /** 33 /**
38 * A CSS attribute used for temporarily hiding a SETTINGS-SECTION for the 34 * A CSS attribute used for temporarily hiding a SETTINGS-SECTION for the
39 * purposes of searching. 35 * purposes of searching.
40 */ 36 */
41 hiddenBySearch: { 37 hiddenBySearch: {
42 type: Boolean, 38 type: Boolean,
43 value: false, 39 value: false,
44 reflectToAttribute: true, 40 reflectToAttribute: true,
45 }, 41 },
42
43 /**
44 * Original height of the collapsed section, used as the target height when
45 * collapsing after being expanded.
46 * TODO(michaelpg): Get the height dynamically when collapsing using the
47 * card's main page.
48 * @private
49 */
50 collapsedHeight_: {
51 type: Number,
52 value: NaN,
53 },
46 }, 54 },
47 55
48 /** 56 /**
49 * Freezes the section's height so its card can be removed from the flow 57 * Freezes the section's height so its card can be removed from the flow
50 * without affecting the layout of the surrounding sections. 58 * without affecting the layout of the surrounding sections.
51 * @param {boolean} frozen True to freeze, false to unfreeze. 59 * @param {boolean} frozen True to freeze, false to unfreeze.
52 * @private 60 * @private
53 */ 61 */
54 setFrozen: function(frozen) { 62 setFrozen: function(frozen) {
55 var card = this.$.card; 63 var card = this.$.card;
56 if (frozen) { 64 if (frozen) {
57 this.style.height = this.clientHeight + 'px'; 65 this.style.height = this.clientHeight + 'px';
58 66
59 var cardHeight = card.offsetHeight; 67 var cardHeight = card.offsetHeight;
60 var cardWidth = card.offsetWidth; 68 var cardWidth = card.offsetWidth;
61 // If the section is not displayed yet (e.g., navigated directly to a 69 // If the section is not displayed yet (e.g., navigated directly to a
62 // sub-page), cardHeight and cardWidth are 0, so do not set the height or 70 // sub-page), cardHeight and cardWidth are 0, so do not set the height or
63 // width explicitly. 71 // width explicitly.
64 // TODO(michaelpg): Improve this logic when refactoring
65 // settings-animated-pages.
66 if (cardHeight && cardWidth) { 72 if (cardHeight && cardWidth) {
67 // TODO(michaelpg): Temporary hack to store the height the section
68 // should collapse to when it closes.
69 card.origHeight_ = cardHeight;
70
71 card.style.height = cardHeight + 'px'; 73 card.style.height = cardHeight + 'px';
72 card.style.width = cardWidth + 'px'; 74 card.style.width = cardWidth + 'px';
73 } else {
74 // Set an invalid value so we don't try to use it later.
75 card.origHeight_ = NaN;
76 } 75 }
77 76
78 // Place the section's card at its current position but removed from the 77 // Place the section's card at its current position but removed from the
79 // flow. 78 // flow.
80 card.style.top = card.getBoundingClientRect().top + 'px'; 79 card.style.top = card.getBoundingClientRect().top + 'px';
81 this.classList.add('frozen'); 80 this.classList.add('frozen');
82 } else { 81 } else {
83 // Restore the section to its normal height. 82 // Restore the section to its normal height.
84 if (!this.classList.contains('frozen')) 83 if (!this.classList.contains('frozen'))
85 return; 84 return;
86 this.classList.remove('frozen'); 85 this.classList.remove('frozen');
87 this.$.card.style.top = ''; 86 this.$.card.style.top = '';
88 this.$.card.style.height = ''; 87 this.$.card.style.height = '';
89 this.$.card.style.width = ''; 88 this.$.card.style.width = '';
90 this.style.height = ''; 89 this.style.height = '';
91 } 90 }
92 }, 91 },
93 92
94 /** 93 /**
95 * @return {boolean} True if the section is currently rendered and not 94 * @return {boolean} True if the section is currently rendered and not
96 * already expanded or transitioning. 95 * already expanded or transitioning.
97 */ 96 */
98 canAnimateExpand: function() { 97 canAnimateExpand: function() {
99 return !this.classList.contains('expanded'); 98 return !this.classList.contains('expanding') &&
99 !this.classList.contains('expanded') && this.$.card.clientHeight > 0;
100 }, 100 },
101 101
102 /** 102 /**
103 * Animates the section expanding to fill the container. The section is fixed 103 * Animates the section expanding to fill the container. The section is fixed
104 * in the viewport during the animation. The section adds the "expanding" 104 * in the viewport during the animation, making it safe to adjust the rest of
105 * class while the animation plays. 105 * the DOM after calling this. The section adds the "expanding" class while
106 * the animation plays and "expanded" after it finishes.
106 * 107 *
107 * @param {!HTMLElement} container The scrolling container to fill. 108 * @param {!HTMLElement} container The scrolling container to fill.
108 * @return {?settings.animation.Animation} Animation played, if any. 109 * @return {!settings.animation.Animation}
109 */ 110 */
110 animateExpand: function(container) { 111 animateExpand: function(container) {
111 var card = this.$.card; 112 // Set the section's height so its card can be removed from the flow
112 113 // without affecting the surrounding sections during the animation.
113 // The card should start at the top of the page. 114 this.collapsedHeight_ = this.clientHeight;
114 var targetTop = container.getBoundingClientRect().top; 115 this.style.height = this.collapsedHeight_ + 'px';
115 116
116 this.classList.add('expanding'); 117 this.classList.add('expanding');
117 118
118 // Nothing to animate. 119 // Start the card in place, at its distance from the container's padding.
119 if (!card.style.top || !card.style.height) 120 var startTop = this.$.card.getBoundingClientRect().top + 'px';
120 return null; 121 var startHeight = this.$.card.clientHeight + 'px';
121 122
122 // Expand the card, using minHeight. (The card must span the container's 123 // Target position is the container's top edge in the viewport.
123 // client height, so it must be at least 100% in case the card is too short. 124 var containerTop = container.getBoundingClientRect().top;
124 // If the card is already taller than the container's client height, we 125 var endTop = containerTop + 'px';
125 // don't want to shrink the card to 100% or the content will overflow, so 126 // The card should stretch from the bottom of the toolbar to the bottom of
126 // we can't use height, and animating height wouldn't look right anyway.) 127 // the page. calc(100% - top) lets the card resize if the window resizes.
127 var keyframes = [{ 128 var endHeight = 'calc(100% - ' + containerTop + 'px)';
128 top: card.style.top,
129 minHeight: card.style.height,
130 easing: EASING_FUNCTION,
131 }, {
132 top: targetTop + 'px',
133 minHeight: 'calc(100% - ' + targetTop + 'px)',
134 }];
135 var options = /** @type {!KeyframeEffectOptions} */({
136 duration: EXPAND_DURATION
137 });
138 // TODO(michaelpg): Change elevation of sections.
139 return new settings.animation.Animation(card, keyframes, options);
140 },
141 129
142 /** 130 var animation =
143 * Cleans up after animateExpand(). 131 this.animateCard_('fixed', startTop, endTop, startHeight, endHeight);
144 * @param {boolean} finished Whether the animation finished successfully. 132 animation.finished.then(function() {
145 */
146 cleanUpAnimateExpand: function(finished) {
147 if (finished) {
148 this.classList.add('expanded'); 133 this.classList.add('expanded');
149 this.$.card.style.top = ''; 134 }.bind(this), function() {}).then(function() {
150 this.$.header.hidden = true; 135 // Unset these changes whether the animation finished or canceled.
136 this.classList.remove('expanding');
151 this.style.height = ''; 137 this.style.height = '';
152 } 138 }.bind(this));
153 139 return animation;
154 this.classList.remove('expanding');
155 this.$.card.style.height = '';
156 this.$.card.style.width = '';
157 }, 140 },
158 141
159 /** @return {boolean} True if the section is currently expanded. */ 142 /** @return {boolean} True if the section is currently expanded. */
160 canAnimateCollapse: function() { 143 canAnimateCollapse: function() {
161 return this.classList.contains('expanded'); 144 return this.classList.contains('expanded') && this.clientHeight > 0 &&
145 !Number.isNaN(this.collapsedHeight_);
146 },
147
148 /**
149 * Prepares for the animation before the other sections become visible.
150 * Call before animateCollapse().
151 * @param {!HTMLElement} container
152 */
153 setUpAnimateCollapse: function(container) {
154 // Prepare the dimensions and set position: fixed.
155 this.$.card.style.width = this.$.card.clientWidth + 'px';
156 this.$.card.style.height = this.$.card.clientHeight + 'px';
157 this.$.card.style.top = container.getBoundingClientRect().top + 'px';
158 this.$.card.style.position = 'fixed';
159
160 // The section can now collapse back into its original height the page so
161 // the other sections appear in the right places.
162 this.classList.remove('expanded');
163 this.classList.add('collapsing');
164 this.style.height = this.collapsedHeight_ + 'px';
162 }, 165 },
163 166
164 /** 167 /**
165 * Collapses an expanded section's card back into position in the main page. 168 * Collapses an expanded section's card back into position in the main page.
166 * Adds the "expanding" class during the animation. 169 * Call after calling animateCollapse(), unhiding other content and scrolling.
167 * @param {!HTMLElement} container The scrolling container the card fills. 170 * @param {!HTMLElement} container The scrolling container the card fills.
168 * @param {number} prevScrollTop scrollTop of the container before this 171 * @return {!settings.animation.Animation}
169 * section expanded.
170 * @return {?settings.animation.Animation} Animation played, if any.
171 */ 172 */
172 animateCollapse: function(container, prevScrollTop) { 173 animateCollapse: function(container) {
173 this.$.header.hidden = false; 174 // Make the card position: absolute, so scrolling is less of a crapshoot.
175 // First find the current distance between this section and the card using
176 // fixed coordinates; the absolute distance will be the same.
177 var fixedCardTop = this.$.card.getBoundingClientRect().top;
178 var fixedSectionTop = this.getBoundingClientRect().top;
179 var distance = fixedCardTop - fixedSectionTop;
174 180
175 var startingTop = container.getBoundingClientRect().top; 181 // The target position is right below our header.
182 var headerStyle = getComputedStyle(this.$.header);
183 var cardTargetTop = this.$.header.offsetHeight +
184 parseFloat(headerStyle.marginBottom) +
185 parseFloat(headerStyle.marginTop);
176 186
177 var card = this.$.card; 187 // Start the card at its current height and distance from our top.
178 var cardHeightStart = card.clientHeight; 188 var startTop = distance + 'px';
179 var cardWidthStart = card.clientWidth; 189 var startHeight = this.$.card.style.height;
180 190
181 this.classList.add('collapsing'); 191 // End at the bottom of our header.
182 this.classList.remove('expanding', 'expanded'); 192 var endTop = cardTargetTop + 'px';
193 var endHeight = (this.collapsedHeight_ - cardTargetTop) + 'px';
183 194
184 // If we navigated here directly, we don't know the original height of the 195 // The card no longer needs position: fixed.
185 // section, so we skip the animation. 196 this.$.card.style.position = '';
186 // TODO(michaelpg): remove this condition once sliding is implemented.
187 if (Number.isNaN(card.origHeight_))
188 return null;
189 197
190 // Restore the section to its proper height to make room for the card. 198 // Collapse this section, animate the card into place, and remove its
191 this.style.height = (this.clientHeight + card.origHeight_) + 'px'; 199 // other properties.
200 var animation =
201 this.animateCard_('absolute', startTop, endTop, startHeight, endHeight);
202 this.$.card.style.width = '';
203 this.$.card.style.height = '';
204 this.$.card.style.top = '';
192 205
193 // TODO(michaelpg): this should be in MainPageBehavior(), but we need to 206 animation.finished.then(function() {
194 // wait until the full page height is available (setting the section 207 this.classList.remove('expanded');
195 // height). 208 }.bind(this), function() {}).then(function() {
196 container.scrollTop = prevScrollTop; 209 // The card now determines the section's height automatically.
197 210 this.style.height = '';
198 // The card is unpositioned, so use its position as the ending state, 211 this.classList.remove('collapsing');
199 // but account for scroll. 212 }.bind(this));
200 var targetTop = card.getBoundingClientRect().top - container.scrollTop; 213 return animation;
201
202 // Account for the section header.
203 var headerStyle = getComputedStyle(this.$.header);
204 targetTop += this.$.header.offsetHeight +
205 parseInt(headerStyle.marginBottom, 10) +
206 parseInt(headerStyle.marginTop, 10);
207
208 var keyframes = [{
209 top: startingTop + 'px',
210 minHeight: cardHeightStart + 'px',
211 easing: EASING_FUNCTION,
212 }, {
213 top: targetTop + 'px',
214 minHeight: card.origHeight_ + 'px',
215 }];
216 var options = /** @type {!KeyframeEffectOptions} */({
217 duration: EXPAND_DURATION
218 });
219
220 card.style.width = cardWidthStart + 'px';
221
222 return new settings.animation.Animation(card, keyframes, options);
223 }, 214 },
224 215
225 /** 216 /**
226 * Cleans up after animateCollapse(). 217 * Helper function to animate the card's position and height.
227 * @param {boolean} finished Whether the animation finished successfully. 218 * @param {string} position CSS position property.
219 * @param {string} startTop Initial top value.
220 * @param {string} endTop Target top value.
221 * @param {string} startHeight Initial height value.
222 * @param {string} endHeight Target height value.
223 * @return {!settings.animation.Animation}
224 * @private
228 */ 225 */
229 cleanUpAnimateCollapse: function(finished) { 226 animateCard_: function(position, startTop, endTop, startHeight, endHeight) {
230 if (finished) 227 // Width does not change.
231 this.$.card.style.width = ''; 228 var width = this.$.card.clientWidth + 'px';
229
230 var startFrame = {
231 position: position,
232 width: width,
233 top: startTop,
234 height: startHeight,
235 };
236
237 var endFrame = {
238 position: position,
239 width: width,
240 top: endTop,
241 height: endHeight,
242 };
243
244 var options = /** @type {!KeyframeEffectOptions} */({
245 duration: settings.animation.Timing.DURATION,
246 easing: settings.animation.Timing.EASING,
247 });
248
249 return new settings.animation.Animation(
250 this.$.card, [startFrame, endFrame], options);
232 }, 251 },
233 }); 252 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698