|
205 | 205 | // scroll animation loop w/ easing
|
206 | 206 | // credit https://gist.github.com/dezinezync/5487119
|
207 | 207 | var start = Date.now(),
|
208 |
| - duration = 1000, //milliseconds |
| 208 | + duration = 250, //milliseconds |
209 | 209 | fromY = self.el.scrollTop,
|
210 | 210 | fromX = self.el.scrollLeft;
|
211 | 211 |
|
|
239 | 239 |
|
240 | 240 | } else {
|
241 | 241 | // done
|
| 242 | + ionic.tap.removeClonedInputs(self.__container, self); |
242 | 243 | self.resize();
|
243 | 244 | }
|
244 | 245 | }
|
|
293 | 294 |
|
294 | 295 | // Event Handler
|
295 | 296 | var container = self.__container;
|
| 297 | + // save height when scroll view is shrunk so we don't need to reflow |
| 298 | + var scrollViewOffsetHeight; |
296 | 299 |
|
297 |
| - // should be unnecessary in native scrolling, but keep in case bugs show up |
298 |
| - self.scrollChildIntoView = NOOP; |
| 300 | + /** |
| 301 | + * Shrink the scroll view when the keyboard is up if necessary and if the |
| 302 | + * focused input is below the bottom of the shrunk scroll view, scroll it |
| 303 | + * into view. |
| 304 | + */ |
| 305 | + self.scrollChildIntoView = function(e) { |
| 306 | + //console.log("scrollChildIntoView at: " + Date.now()); |
| 307 | + |
| 308 | + // D |
| 309 | + var scrollBottomOffsetToTop = container.getBoundingClientRect().bottom; |
| 310 | + // D - A |
| 311 | + scrollViewOffsetHeight = container.offsetHeight; |
| 312 | + var alreadyShrunk = self.isShrunkForKeyboard; |
| 313 | + |
| 314 | + var isModal = container.parentNode.classList.contains('modal'); |
| 315 | + // 680px is when the media query for 60% modal width kicks in |
| 316 | + var isInsetModal = isModal && window.innerWidth >= 680; |
| 317 | + |
| 318 | + /* |
| 319 | + * _______ |
| 320 | + * |---A---| <- top of scroll view |
| 321 | + * | | |
| 322 | + * |---B---| <- keyboard |
| 323 | + * | C | <- input |
| 324 | + * |---D---| <- initial bottom of scroll view |
| 325 | + * |___E___| <- bottom of viewport |
| 326 | + * |
| 327 | + * All commented calculations relative to the top of the viewport (ie E |
| 328 | + * is the viewport height, not 0) |
| 329 | + */ |
| 330 | + if (!alreadyShrunk) { |
| 331 | + // shrink scrollview so we can actually scroll if the input is hidden |
| 332 | + // if it isn't shrink so we can scroll to inputs under the keyboard |
| 333 | + // inset modals won't shrink on Android on their own when the keyboard appears |
| 334 | + if ( ionic.Platform.isIOS() || ionic.Platform.isFullScreen || isInsetModal ) { |
| 335 | + // if there are things below the scroll view account for them and |
| 336 | + // subtract them from the keyboard height when resizing |
| 337 | + // E - D E D |
| 338 | + var scrollBottomOffsetToBottom = e.detail.viewportHeight - scrollBottomOffsetToTop; |
| 339 | + |
| 340 | + // 0 or D - B if D > B E - B E - D |
| 341 | + var keyboardOffset = Math.max(0, e.detail.keyboardHeight - scrollBottomOffsetToBottom); |
| 342 | + |
| 343 | + ionic.requestAnimationFrame(function(){ |
| 344 | + // D - A or B - A if D > B D - A max(0, D - B) |
| 345 | + scrollViewOffsetHeight = scrollViewOffsetHeight - keyboardOffset; |
| 346 | + container.style.height = scrollViewOffsetHeight + "px"; |
| 347 | + |
| 348 | + //update scroll view |
| 349 | + self.resize(); |
| 350 | + }); |
| 351 | + } |
| 352 | + |
| 353 | + self.isShrunkForKeyboard = true; |
| 354 | + } |
| 355 | + |
| 356 | + /* |
| 357 | + * _______ |
| 358 | + * |---A---| <- top of scroll view |
| 359 | + * | * | <- where we want to scroll to |
| 360 | + * |--B-D--| <- keyboard, bottom of scroll view |
| 361 | + * | C | <- input |
| 362 | + * | | |
| 363 | + * |___E___| <- bottom of viewport |
| 364 | + * |
| 365 | + * All commented calculations relative to the top of the viewport (ie E |
| 366 | + * is the viewport height, not 0) |
| 367 | + */ |
| 368 | + // if the element is positioned under the keyboard scroll it into view |
| 369 | + if (e.detail.isElementUnderKeyboard) { |
| 370 | + |
| 371 | + ionic.requestAnimationFrame(function(){ |
| 372 | + // update D if we shrunk |
| 373 | + if (self.isShrunkForKeyboard && !alreadyShrunk) { |
| 374 | + scrollBottomOffsetToTop = container.getBoundingClientRect().bottom; |
| 375 | + } |
| 376 | + |
| 377 | + // middle of the scrollview, this is where we want to scroll to |
| 378 | + // (D - A) / 2 |
| 379 | + var scrollMidpointOffset = scrollViewOffsetHeight * 0.5; |
| 380 | + //console.log("container.offsetHeight: " + scrollViewOffsetHeight); |
| 381 | + |
| 382 | + // middle of the input we want to scroll into view |
| 383 | + // C |
| 384 | + var inputMidpoint = ((e.detail.elementBottom + e.detail.elementTop) / 2); |
| 385 | + |
| 386 | + // distance from middle of input to the bottom of the scroll view |
| 387 | + // C - D C D |
| 388 | + var inputMidpointOffsetToScrollBottom = inputMidpoint - scrollBottomOffsetToTop; |
| 389 | + |
| 390 | + //C - D + (D - A)/2 C - D (D - A)/ 2 |
| 391 | + var scrollTop = inputMidpointOffsetToScrollBottom + scrollMidpointOffset; |
| 392 | + |
| 393 | + if ( scrollTop > 0) { |
| 394 | + if (ionic.Platform.isIOS()) { |
| 395 | + //just shrank scroll view, give it some breathing room before scrolling |
| 396 | + setTimeout(function(){ |
| 397 | + ionic.tap.cloneFocusedInput(container, self); |
| 398 | + self.scrollBy(0, scrollTop, true); |
| 399 | + self.onScroll(); |
| 400 | + }, 32); |
| 401 | + } else { |
| 402 | + self.scrollBy(0, scrollTop, true); |
| 403 | + self.onScroll(); |
| 404 | + } |
| 405 | + } |
| 406 | + }); |
| 407 | + } |
| 408 | + |
| 409 | + // Only the first scrollView parent of the element that broadcasted this event |
| 410 | + // (the active element that needs to be shown) should receive this event |
| 411 | + e.stopPropagation(); |
| 412 | + }; |
299 | 413 |
|
300 | 414 | self.resetScrollView = function() {
|
301 | 415 | //return scrollview to original height once keyboard has hidden
|
302 |
| - if (self.isScrolledIntoView) { |
303 |
| - self.isScrolledIntoView = false; |
| 416 | + if (self.isShrunkForKeyboard) { |
| 417 | + self.isShrunkForKeyboard = false; |
304 | 418 | container.style.height = "";
|
305 |
| - container.style.overflow = ""; |
306 |
| - self.resize(); |
307 |
| - ionic.scroll.isScrolling = false; |
308 | 419 | }
|
| 420 | + self.resize(); |
309 | 421 | };
|
310 | 422 |
|
311 |
| - container.addEventListener('resetScrollView', self.resetScrollView); |
312 | 423 | container.addEventListener('scroll', self.onScroll);
|
313 | 424 |
|
314 | 425 | //Broadcasted when keyboard is shown on some platforms.
|
315 | 426 | //See js/utils/keyboard.js
|
316 | 427 | container.addEventListener('scrollChildIntoView', self.scrollChildIntoView);
|
317 |
| - container.addEventListener('resetScrollView', self.resetScrollView); |
| 428 | + |
| 429 | + // Listen on document because container may not have had the last |
| 430 | + // keyboardActiveElement, for example after closing a modal with a focused |
| 431 | + // input and returning to a previously resized scroll view in an ion-content. |
| 432 | + // Since we can only resize scroll views that are currently visible, just resize |
| 433 | + // the current scroll view when the keyboard is closed. |
| 434 | + document.addEventListener('resetScrollView', self.resetScrollView); |
318 | 435 | },
|
319 | 436 |
|
320 | 437 | __cleanup: function() {
|
|
336 | 453 | delete self.options.el;
|
337 | 454 |
|
338 | 455 | self.resize = self.scrollTo = self.onScroll = self.resetScrollView = NOOP;
|
| 456 | + self.scrollChildIntoView = NOOP; |
339 | 457 | container = null;
|
340 | 458 | }
|
341 | 459 | });
|
|
0 commit comments