123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877
  1. import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, NgZone, Optional, Output, Renderer, ViewChild, ViewEncapsulation } from '@angular/core';
  2. import { App } from '../app/app';
  3. import { Config } from '../../config/config';
  4. import { DomController } from '../../platform/dom-controller';
  5. import { Ion } from '../ion';
  6. import { isTabs } from '../../navigation/nav-util';
  7. import { isTrueProperty, removeArrayItem } from '../../util/util';
  8. import { Keyboard } from '../../platform/keyboard';
  9. import { NavController } from '../../navigation/nav-controller';
  10. import { Platform } from '../../platform/platform';
  11. import { ScrollView } from '../../util/scroll-view';
  12. import { ViewController } from '../../navigation/view-controller';
  13. export class EventEmitterProxy extends EventEmitter {
  14. subscribe(generatorOrNext, error, complete) {
  15. this.onSubscribe();
  16. return super.subscribe(generatorOrNext, error, complete);
  17. }
  18. }
  19. /**
  20. * @name Content
  21. * @description
  22. * The Content component provides an easy to use content area with
  23. * some useful methods to control the scrollable area. There should
  24. * only be one content in a single view component. If additional scrollable
  25. * elements are needed, use [ionScroll](../../scroll/Scroll).
  26. *
  27. *
  28. * The content area can also implement pull-to-refresh with the
  29. * [Refresher](../../refresher/Refresher) component.
  30. *
  31. * @usage
  32. * ```html
  33. * <ion-content>
  34. * Add your content here!
  35. * </ion-content>
  36. * ```
  37. *
  38. * To get a reference to the content component from a Page's logic,
  39. * you can use Angular's `@ViewChild` annotation:
  40. *
  41. * ```ts
  42. * import { Component, ViewChild } from '@angular/core';
  43. * import { Content } from 'ionic-angular';
  44. *
  45. * @Component({...})
  46. * export class MyPage{
  47. * @ViewChild(Content) content: Content;
  48. *
  49. * scrollToTop() {
  50. * this.content.scrollToTop();
  51. * }
  52. * }
  53. * ```
  54. *
  55. * @advanced
  56. *
  57. * ### Scroll Events
  58. *
  59. * Scroll events happen outside of Angular's Zones. This is for performance reasons. So
  60. * if you're trying to bind a value to any scroll event, it will need to be wrapped in
  61. * a `zone.run()`
  62. *
  63. * ```ts
  64. * import { Component, NgZone } from '@angular/core';
  65. * @Component({
  66. * template: `
  67. * <ion-header>
  68. * <ion-navbar>
  69. * <ion-title>{{scrollAmount}}</ion-title>
  70. * </ion-navbar>
  71. * </ion-header>
  72. * <ion-content (ionScroll)="scrollHandler($event)">
  73. * <p> Some realllllllly long content </p>
  74. * </ion-content>
  75. * `})
  76. * class E2EPage {
  77. * public scrollAmount = 0;
  78. * constructor( public zone: NgZone){}
  79. * scrollHandler(event) {
  80. * console.log(`ScrollEvent: ${event}`)
  81. * this.zone.run(()=>{
  82. * // since scrollAmount is data-binded,
  83. * // the update needs to happen in zone
  84. * this.scrollAmount++
  85. * })
  86. * }
  87. * }
  88. * ```
  89. *
  90. * This goes for any scroll event, not just `ionScroll`.
  91. *
  92. * ### Resizing the content
  93. *
  94. * If the height of `ion-header`, `ion-footer` or `ion-tabbar`
  95. * changes dynamically, `content.resize()` has to be called in order to update the
  96. * layout of `Content`.
  97. *
  98. *
  99. * ```ts
  100. * @Component({
  101. * template: `
  102. * <ion-header>
  103. * <ion-navbar>
  104. * <ion-title>Main Navbar</ion-title>
  105. * </ion-navbar>
  106. * <ion-toolbar *ngIf="showToolbar">
  107. * <ion-title>Dynamic Toolbar</ion-title>
  108. * </ion-toolbar>
  109. * </ion-header>
  110. * <ion-content>
  111. * <button ion-button (click)="toggleToolbar()">Toggle Toolbar</button>
  112. * </ion-content>
  113. * `})
  114. *
  115. * class E2EPage {
  116. * @ViewChild(Content) content: Content;
  117. * showToolbar: boolean = false;
  118. *
  119. * toggleToolbar() {
  120. * this.showToolbar = !this.showToolbar;
  121. * this.content.resize();
  122. * }
  123. * }
  124. * ```
  125. *
  126. *
  127. * Scroll to a specific position
  128. *
  129. * ```ts
  130. * import { Component, ViewChild } from '@angular/core';
  131. * import { Content } from 'ionic-angular';
  132. *
  133. * @Component({
  134. * template: `<ion-content>
  135. * <button ion-button (click)="scrollTo()">Down 500px</button>
  136. * </ion-content>`
  137. * )}
  138. * export class MyPage{
  139. * @ViewChild(Content) content: Content;
  140. *
  141. * scrollTo() {
  142. * // set the scrollLeft to 0px, and scrollTop to 500px
  143. * // the scroll duration should take 200ms
  144. * this.content.scrollTo(0, 500, 200);
  145. * }
  146. * }
  147. * ```
  148. *
  149. */
  150. export class Content extends Ion {
  151. constructor(config, _plt, _dom, elementRef, renderer, _app, _keyboard, _zone, viewCtrl, navCtrl) {
  152. super(config, elementRef, renderer, 'content');
  153. this._plt = _plt;
  154. this._dom = _dom;
  155. this._app = _app;
  156. this._keyboard = _keyboard;
  157. this._zone = _zone;
  158. /** @internal */
  159. this._scrollPadding = 0;
  160. /** @internal */
  161. this._inputPolling = false;
  162. /** @internal */
  163. this._hasRefresher = false;
  164. /** @internal */
  165. this._imgs = [];
  166. /** @internal */
  167. this._scrollDownOnLoad = false;
  168. /**
  169. * @output {ScrollEvent} Emitted when the scrolling first starts.
  170. */
  171. this.ionScrollStart = new EventEmitterProxy();
  172. /**
  173. * @output {ScrollEvent} Emitted on every scroll event.
  174. */
  175. this.ionScroll = new EventEmitterProxy();
  176. /**
  177. * @output {ScrollEvent} Emitted when scrolling ends.
  178. */
  179. this.ionScrollEnd = new EventEmitterProxy();
  180. const enableScrollListener = () => this._scroll.enableEvents();
  181. this.ionScroll.onSubscribe = enableScrollListener;
  182. this.ionScrollStart.onSubscribe = enableScrollListener;
  183. this.ionScrollEnd.onSubscribe = enableScrollListener;
  184. this.statusbarPadding = config.getBoolean('statusbarPadding', false);
  185. this._imgReqBfr = config.getNumber('imgRequestBuffer', 1400);
  186. this._imgRndBfr = config.getNumber('imgRenderBuffer', 400);
  187. this._imgVelMax = config.getNumber('imgVelocityMax', 3);
  188. this._scroll = new ScrollView(_app, _plt, _dom);
  189. while (navCtrl) {
  190. if (isTabs(navCtrl)) {
  191. this._tabs = navCtrl;
  192. break;
  193. }
  194. navCtrl = navCtrl.parent;
  195. }
  196. if (viewCtrl) {
  197. this._viewCtrl = viewCtrl;
  198. // content has a view controller
  199. viewCtrl._setIONContent(this);
  200. viewCtrl._setIONContentRef(elementRef);
  201. this._viewCtrlReadSub = viewCtrl.readReady.subscribe(() => {
  202. this._viewCtrlReadSub.unsubscribe();
  203. this._readDimensions();
  204. });
  205. this._viewCtrlWriteSub = viewCtrl.writeReady.subscribe(() => {
  206. this._viewCtrlWriteSub.unsubscribe();
  207. this._writeDimensions();
  208. });
  209. }
  210. else {
  211. // content does not have a view controller
  212. _dom.read(this._readDimensions.bind(this));
  213. _dom.write(this._writeDimensions.bind(this));
  214. }
  215. }
  216. /**
  217. * Content height of the viewable area. This does not include content
  218. * which is outside the overflow area, or content area which is under
  219. * headers and footers. Read-only.
  220. *
  221. * @return {number}
  222. */
  223. get contentHeight() {
  224. return this._scroll.ev.contentHeight;
  225. }
  226. /**
  227. * Content width including content which is not visible on the screen
  228. * due to overflow. Read-only.
  229. *
  230. * @return {number}
  231. */
  232. get contentWidth() {
  233. return this._scroll.ev.contentWidth;
  234. }
  235. /**
  236. * Content height including content which is not visible on the screen
  237. * due to overflow. Read-only.
  238. *
  239. * @return {number}
  240. */
  241. get scrollHeight() {
  242. return this._scroll.ev.scrollHeight;
  243. }
  244. /**
  245. * Content width including content which is not visible due to
  246. * overflow. Read-only.
  247. *
  248. * @return {number}
  249. */
  250. get scrollWidth() {
  251. return this._scroll.ev.scrollWidth;
  252. }
  253. /**
  254. * The distance of the content's top to its topmost visible content.
  255. *
  256. * @return {number}
  257. */
  258. get scrollTop() {
  259. return this._scroll.ev.scrollTop;
  260. }
  261. /**
  262. * @param {number} top
  263. */
  264. set scrollTop(top) {
  265. this._scroll.setTop(top);
  266. }
  267. /**
  268. * The distance of the content's left to its leftmost visible content.
  269. *
  270. * @return {number}
  271. */
  272. get scrollLeft() {
  273. return this._scroll.ev.scrollLeft;
  274. }
  275. /**
  276. * @param {number} top
  277. */
  278. set scrollLeft(top) {
  279. this._scroll.setLeft(top);
  280. }
  281. /**
  282. * If the content is actively scrolling or not.
  283. *
  284. * @return {boolean}
  285. */
  286. get isScrolling() {
  287. return this._scroll.isScrolling;
  288. }
  289. /**
  290. * The current, or last known, vertical scroll direction. Possible
  291. * string values include `down` and `up`.
  292. *
  293. * @return {string}
  294. */
  295. get directionY() {
  296. return this._scroll.ev.directionY;
  297. }
  298. /**
  299. * The current, or last known, horizontal scroll direction. Possible
  300. * string values include `right` and `left`.
  301. *
  302. * @return {string}
  303. */
  304. get directionX() {
  305. return this._scroll.ev.directionX;
  306. }
  307. /**
  308. * @hidden
  309. */
  310. ngAfterViewInit() {
  311. (void 0) /* assert */;
  312. (void 0) /* assert */;
  313. const scroll = this._scroll;
  314. scroll.ev.fixedElement = this.getFixedElement();
  315. scroll.ev.scrollElement = this.getScrollElement();
  316. // subscribe to the scroll start
  317. scroll.onScrollStart = (ev) => {
  318. this.ionScrollStart.emit(ev);
  319. };
  320. // subscribe to every scroll move
  321. scroll.onScroll = (ev) => {
  322. // emit to all of our other friends things be scrolling
  323. this.ionScroll.emit(ev);
  324. this.imgsUpdate();
  325. };
  326. // subscribe to the scroll end
  327. scroll.onScrollEnd = (ev) => {
  328. this.ionScrollEnd.emit(ev);
  329. this.imgsUpdate();
  330. };
  331. }
  332. /**
  333. * @hidden
  334. */
  335. enableJsScroll() {
  336. this._scroll.enableJsScroll(this._cTop, this._cBottom);
  337. }
  338. /**
  339. * @hidden
  340. */
  341. ngOnDestroy() {
  342. this._scLsn && this._scLsn();
  343. this._viewCtrlReadSub && this._viewCtrlReadSub.unsubscribe();
  344. this._viewCtrlWriteSub && this._viewCtrlWriteSub.unsubscribe();
  345. this._viewCtrlReadSub = this._viewCtrlWriteSub = null;
  346. this._scroll && this._scroll.destroy();
  347. this._footerEle = this._scLsn = this._scroll = null;
  348. }
  349. /**
  350. * @hidden
  351. */
  352. getScrollElement() {
  353. return this._scrollContent.nativeElement;
  354. }
  355. /**
  356. * @private
  357. */
  358. getFixedElement() {
  359. return this._fixedContent.nativeElement;
  360. }
  361. /**
  362. * @hidden
  363. */
  364. onScrollElementTransitionEnd(callback) {
  365. this._plt.transitionEnd(this.getScrollElement(), callback);
  366. }
  367. /**
  368. * Scroll to the specified position.
  369. *
  370. * @param {number} x The x-value to scroll to.
  371. * @param {number} y The y-value to scroll to.
  372. * @param {number} [duration] Duration of the scroll animation in milliseconds. Defaults to `300`.
  373. * @returns {Promise} Returns a promise which is resolved when the scroll has completed.
  374. */
  375. scrollTo(x, y, duration = 300, done) {
  376. (void 0) /* console.debug */;
  377. return this._scroll.scrollTo(x, y, duration, done);
  378. }
  379. /**
  380. * Scroll to the top of the content component.
  381. *
  382. * @param {number} [duration] Duration of the scroll animation in milliseconds. Defaults to `300`.
  383. * @returns {Promise} Returns a promise which is resolved when the scroll has completed.
  384. */
  385. scrollToTop(duration = 300) {
  386. (void 0) /* console.debug */;
  387. return this._scroll.scrollToTop(duration);
  388. }
  389. /**
  390. * Scroll to the bottom of the content component.
  391. *
  392. * @param {number} [duration] Duration of the scroll animation in milliseconds. Defaults to `300`.
  393. * @returns {Promise} Returns a promise which is resolved when the scroll has completed.
  394. */
  395. scrollToBottom(duration = 300) {
  396. (void 0) /* console.debug */;
  397. return this._scroll.scrollToBottom(duration);
  398. }
  399. /**
  400. * @input {boolean} If true, the content will scroll behind the headers
  401. * and footers. This effect can easily be seen by setting the toolbar
  402. * to transparent.
  403. */
  404. get fullscreen() {
  405. return this._fullscreen;
  406. }
  407. set fullscreen(val) {
  408. this._fullscreen = isTrueProperty(val);
  409. }
  410. /**
  411. * @input {boolean} If true, the content will scroll down on load.
  412. */
  413. get scrollDownOnLoad() {
  414. return this._scrollDownOnLoad;
  415. }
  416. set scrollDownOnLoad(val) {
  417. this._scrollDownOnLoad = isTrueProperty(val);
  418. }
  419. /**
  420. * @private
  421. */
  422. addImg(img) {
  423. this._imgs.push(img);
  424. }
  425. /**
  426. * @hidden
  427. */
  428. removeImg(img) {
  429. removeArrayItem(this._imgs, img);
  430. }
  431. /**
  432. * @hidden
  433. * DOM WRITE
  434. */
  435. setScrollElementStyle(prop, val) {
  436. const scrollEle = this.getScrollElement();
  437. if (scrollEle) {
  438. this._dom.write(() => {
  439. scrollEle.style[prop] = val;
  440. });
  441. }
  442. }
  443. /**
  444. * Returns the content and scroll elements' dimensions.
  445. * @returns {object} dimensions The content and scroll elements' dimensions
  446. * {number} dimensions.contentHeight content offsetHeight
  447. * {number} dimensions.contentTop content offsetTop
  448. * {number} dimensions.contentBottom content offsetTop+offsetHeight
  449. * {number} dimensions.contentWidth content offsetWidth
  450. * {number} dimensions.contentLeft content offsetLeft
  451. * {number} dimensions.contentRight content offsetLeft + offsetWidth
  452. * {number} dimensions.scrollHeight scroll scrollHeight
  453. * {number} dimensions.scrollTop scroll scrollTop
  454. * {number} dimensions.scrollBottom scroll scrollTop + scrollHeight
  455. * {number} dimensions.scrollWidth scroll scrollWidth
  456. * {number} dimensions.scrollLeft scroll scrollLeft
  457. * {number} dimensions.scrollRight scroll scrollLeft + scrollWidth
  458. */
  459. getContentDimensions() {
  460. const scrollEle = this.getScrollElement();
  461. const parentElement = scrollEle.parentElement;
  462. return {
  463. contentHeight: parentElement.offsetHeight - this._cTop - this._cBottom,
  464. contentTop: this._cTop,
  465. contentBottom: this._cBottom,
  466. contentWidth: parentElement.offsetWidth,
  467. contentLeft: parentElement.offsetLeft,
  468. scrollHeight: scrollEle.scrollHeight,
  469. scrollTop: scrollEle.scrollTop,
  470. scrollWidth: scrollEle.scrollWidth,
  471. scrollLeft: scrollEle.scrollLeft,
  472. };
  473. }
  474. /**
  475. * @hidden
  476. * DOM WRITE
  477. * Adds padding to the bottom of the scroll element when the keyboard is open
  478. * so content below the keyboard can be scrolled into view.
  479. */
  480. addScrollPadding(newPadding) {
  481. (void 0) /* assert */;
  482. if (newPadding === 0) {
  483. this._inputPolling = false;
  484. this._scrollPadding = -1;
  485. }
  486. if (newPadding > this._scrollPadding) {
  487. (void 0) /* console.debug */;
  488. this._scrollPadding = newPadding;
  489. var scrollEle = this.getScrollElement();
  490. if (scrollEle) {
  491. this._dom.write(() => {
  492. scrollEle.style.paddingBottom = (newPadding > 0) ? newPadding + 'px' : '';
  493. });
  494. }
  495. }
  496. }
  497. /**
  498. * @hidden
  499. * DOM WRITE
  500. */
  501. clearScrollPaddingFocusOut() {
  502. if (!this._inputPolling) {
  503. (void 0) /* console.debug */;
  504. this._inputPolling = true;
  505. this._keyboard.onClose(() => {
  506. (void 0) /* console.debug */;
  507. this.addScrollPadding(0);
  508. }, 200, 3000);
  509. }
  510. }
  511. /**
  512. * Tell the content to recalculate its dimensions. This should be called
  513. * after dynamically adding/removing headers, footers, or tabs.
  514. */
  515. resize() {
  516. this._dom.read(this._readDimensions.bind(this));
  517. this._dom.write(this._writeDimensions.bind(this));
  518. }
  519. /**
  520. * @hidden
  521. * DOM READ
  522. */
  523. _readDimensions() {
  524. const cachePaddingTop = this._pTop;
  525. const cachePaddingRight = this._pRight;
  526. const cachePaddingBottom = this._pBottom;
  527. const cachePaddingLeft = this._pLeft;
  528. const cacheHeaderHeight = this._hdrHeight;
  529. const cacheFooterHeight = this._ftrHeight;
  530. const cacheTabsPlacement = this._tabsPlacement;
  531. let tabsTop = 0;
  532. let scrollEvent;
  533. this._pTop = 0;
  534. this._pRight = 0;
  535. this._pBottom = 0;
  536. this._pLeft = 0;
  537. this._hdrHeight = 0;
  538. this._ftrHeight = 0;
  539. this._tabsPlacement = null;
  540. this._tTop = 0;
  541. this._fTop = 0;
  542. this._fBottom = 0;
  543. // In certain cases this._scroll is undefined
  544. // if that is the case then we should just return
  545. if (!this._scroll) {
  546. return;
  547. }
  548. scrollEvent = this._scroll.ev;
  549. let ele = this.getNativeElement();
  550. if (!ele) {
  551. (void 0) /* assert */;
  552. return;
  553. }
  554. let computedStyle;
  555. let tagName;
  556. let parentEle = ele.parentElement;
  557. let children = parentEle.children;
  558. for (var i = children.length - 1; i >= 0; i--) {
  559. ele = children[i];
  560. tagName = ele.tagName;
  561. if (tagName === 'ION-CONTENT') {
  562. scrollEvent.contentElement = ele;
  563. if (this._fullscreen) {
  564. // ******** DOM READ ****************
  565. computedStyle = getComputedStyle(ele);
  566. this._pTop = parsePxUnit(computedStyle.paddingTop);
  567. this._pBottom = parsePxUnit(computedStyle.paddingBottom);
  568. this._pRight = parsePxUnit(computedStyle.paddingRight);
  569. this._pLeft = parsePxUnit(computedStyle.paddingLeft);
  570. }
  571. }
  572. else if (tagName === 'ION-HEADER') {
  573. scrollEvent.headerElement = ele;
  574. // ******** DOM READ ****************
  575. this._hdrHeight = ele.clientHeight;
  576. }
  577. else if (tagName === 'ION-FOOTER') {
  578. scrollEvent.footerElement = ele;
  579. // ******** DOM READ ****************
  580. this._ftrHeight = ele.clientHeight;
  581. this._footerEle = ele;
  582. }
  583. }
  584. ele = parentEle;
  585. let tabbarEle;
  586. while (ele && ele.tagName !== 'ION-MODAL' && !ele.classList.contains('tab-subpage')) {
  587. if (ele.tagName === 'ION-TABS') {
  588. tabbarEle = ele.firstElementChild;
  589. // ******** DOM READ ****************
  590. this._tabbarHeight = tabbarEle.clientHeight;
  591. if (this._tabsPlacement === null) {
  592. // this is the first tabbar found, remember it's position
  593. this._tabsPlacement = ele.getAttribute('tabsplacement');
  594. }
  595. }
  596. ele = ele.parentElement;
  597. }
  598. // Tabs top
  599. if (this._tabs && this._tabsPlacement === 'top') {
  600. this._tTop = this._hdrHeight;
  601. tabsTop = this._tabs._top;
  602. }
  603. // Toolbar height
  604. this._cTop = this._hdrHeight;
  605. this._cBottom = this._ftrHeight;
  606. // Tabs height
  607. if (this._tabsPlacement === 'top') {
  608. this._cTop += this._tabbarHeight;
  609. }
  610. else if (this._tabsPlacement === 'bottom') {
  611. this._cBottom += this._tabbarHeight;
  612. }
  613. // Refresher uses a border which should be hidden unless pulled
  614. if (this._hasRefresher) {
  615. this._cTop -= 1;
  616. }
  617. // Fixed content shouldn't include content padding
  618. this._fTop = this._cTop;
  619. this._fBottom = this._cBottom;
  620. // Handle fullscreen viewport (padding vs margin)
  621. if (this._fullscreen) {
  622. this._cTop += this._pTop;
  623. this._cBottom += this._pBottom;
  624. }
  625. // ******** DOM READ ****************
  626. const contentDimensions = this.getContentDimensions();
  627. scrollEvent.scrollHeight = contentDimensions.scrollHeight;
  628. scrollEvent.scrollWidth = contentDimensions.scrollWidth;
  629. scrollEvent.contentHeight = contentDimensions.contentHeight;
  630. scrollEvent.contentWidth = contentDimensions.contentWidth;
  631. scrollEvent.contentTop = contentDimensions.contentTop;
  632. scrollEvent.contentBottom = contentDimensions.contentBottom;
  633. this._dirty = (cachePaddingTop !== this._pTop ||
  634. cachePaddingBottom !== this._pBottom ||
  635. cachePaddingLeft !== this._pLeft ||
  636. cachePaddingRight !== this._pRight ||
  637. cacheHeaderHeight !== this._hdrHeight ||
  638. cacheFooterHeight !== this._ftrHeight ||
  639. cacheTabsPlacement !== this._tabsPlacement ||
  640. tabsTop !== this._tTop ||
  641. this._cTop !== this.contentTop ||
  642. this._cBottom !== this.contentBottom);
  643. this._scroll.init(this.getScrollElement(), this._cTop, this._cBottom);
  644. // initial imgs refresh
  645. this.imgsUpdate();
  646. }
  647. /**
  648. * @hidden
  649. * DOM WRITE
  650. */
  651. _writeDimensions() {
  652. if (!this._dirty) {
  653. (void 0) /* console.debug */;
  654. return;
  655. }
  656. const scrollEle = this.getScrollElement();
  657. if (!scrollEle) {
  658. (void 0) /* assert */;
  659. return;
  660. }
  661. const fixedEle = this.getFixedElement();
  662. if (!fixedEle) {
  663. (void 0) /* assert */;
  664. return;
  665. }
  666. // Tabs height
  667. if (this._tabsPlacement === 'bottom' && this._cBottom > 0 && this._footerEle) {
  668. var footerPos = this._cBottom - this._ftrHeight;
  669. (void 0) /* assert */;
  670. // ******** DOM WRITE ****************
  671. this._footerEle.style.bottom = cssFormat(footerPos);
  672. }
  673. // Handle fullscreen viewport (padding vs margin)
  674. let topProperty = 'marginTop';
  675. let bottomProperty = 'marginBottom';
  676. let fixedTop = this._fTop;
  677. let fixedBottom = this._fBottom;
  678. if (this._fullscreen) {
  679. (void 0) /* assert */;
  680. (void 0) /* assert */;
  681. // adjust the content with padding, allowing content to scroll under headers/footers
  682. // however, on iOS you cannot control the margins of the scrollbar (last tested iOS9.2)
  683. // only add inline padding styles if the computed padding value, which would
  684. // have come from the app's css, is different than the new padding value
  685. topProperty = 'paddingTop';
  686. bottomProperty = 'paddingBottom';
  687. }
  688. // Only update top margin if value changed
  689. if (this._cTop !== this.contentTop) {
  690. (void 0) /* assert */;
  691. (void 0) /* assert */;
  692. // ******** DOM WRITE ****************
  693. scrollEle.style[topProperty] = cssFormat(this._cTop);
  694. // ******** DOM WRITE ****************
  695. fixedEle.style.marginTop = cssFormat(fixedTop);
  696. this.contentTop = this._cTop;
  697. }
  698. // Only update bottom margin if value changed
  699. if (this._cBottom !== this.contentBottom) {
  700. (void 0) /* assert */;
  701. (void 0) /* assert */;
  702. // ******** DOM WRITE ****************
  703. scrollEle.style[bottomProperty] = cssFormat(this._cBottom);
  704. // ******** DOM WRITE ****************
  705. fixedEle.style.marginBottom = cssFormat(fixedBottom);
  706. this.contentBottom = this._cBottom;
  707. }
  708. if (this._tabsPlacement !== null && this._tabs) {
  709. // set the position of the tabbar
  710. if (this._tabsPlacement === 'top') {
  711. // ******** DOM WRITE ****************
  712. this._tabs.setTabbarPosition(this._tTop, -1);
  713. }
  714. else {
  715. (void 0) /* assert */;
  716. // ******** DOM WRITE ****************
  717. this._tabs.setTabbarPosition(-1, 0);
  718. }
  719. }
  720. // Scroll the page all the way down after setting dimensions
  721. if (this._scrollDownOnLoad) {
  722. this.scrollToBottom(0);
  723. this._scrollDownOnLoad = false;
  724. }
  725. }
  726. /**
  727. * @hidden
  728. */
  729. imgsUpdate() {
  730. if (this._scroll.initialized && this._imgs.length && this.isImgsUpdatable()) {
  731. updateImgs(this._imgs, this.scrollTop, this.contentHeight, this.directionY, this._imgReqBfr, this._imgRndBfr);
  732. }
  733. }
  734. /**
  735. * @hidden
  736. */
  737. isImgsUpdatable() {
  738. // an image is only "updatable" if the content isn't scrolling too fast
  739. // if scroll speed is above the maximum velocity, then let current
  740. // requests finish, but do not start new requets or render anything
  741. // if scroll speed is below the maximum velocity, then it's ok
  742. // to start new requests and render images
  743. return Math.abs(this._scroll.ev.velocityY) < this._imgVelMax;
  744. }
  745. }
  746. Content.decorators = [
  747. { type: Component, args: [{
  748. selector: 'ion-content',
  749. template: '<div class="fixed-content" #fixedContent>' +
  750. '<ng-content select="[ion-fixed],ion-fab"></ng-content>' +
  751. '</div>' +
  752. '<div class="scroll-content" #scrollContent>' +
  753. '<ng-content></ng-content>' +
  754. '</div>' +
  755. '<ng-content select="ion-refresher"></ng-content>',
  756. host: {
  757. '[class.statusbar-padding]': 'statusbarPadding',
  758. '[class.has-refresher]': '_hasRefresher'
  759. },
  760. changeDetection: ChangeDetectionStrategy.OnPush,
  761. encapsulation: ViewEncapsulation.None
  762. },] },
  763. ];
  764. /** @nocollapse */
  765. Content.ctorParameters = () => [
  766. { type: Config, },
  767. { type: Platform, },
  768. { type: DomController, },
  769. { type: ElementRef, },
  770. { type: Renderer, },
  771. { type: App, },
  772. { type: Keyboard, },
  773. { type: NgZone, },
  774. { type: ViewController, decorators: [{ type: Optional },] },
  775. { type: NavController, decorators: [{ type: Optional },] },
  776. ];
  777. Content.propDecorators = {
  778. '_fixedContent': [{ type: ViewChild, args: ['fixedContent', { read: ElementRef },] },],
  779. '_scrollContent': [{ type: ViewChild, args: ['scrollContent', { read: ElementRef },] },],
  780. 'ionScrollStart': [{ type: Output },],
  781. 'ionScroll': [{ type: Output },],
  782. 'ionScrollEnd': [{ type: Output },],
  783. 'fullscreen': [{ type: Input },],
  784. 'scrollDownOnLoad': [{ type: Input },],
  785. };
  786. export function updateImgs(imgs, viewableTop, contentHeight, scrollDirectionY, requestableBuffer, renderableBuffer) {
  787. // ok, so it's time to see which images, if any, should be requested and rendered
  788. // ultimately, if we're scrolling fast then don't bother requesting or rendering
  789. // when scrolling is done, then it needs to do a check to see which images are
  790. // important to request and render, and which image requests should be aborted.
  791. // Additionally, images which are not near the viewable area should not be
  792. // rendered at all in order to save browser resources.
  793. const viewableBottom = (viewableTop + contentHeight);
  794. const priority1 = [];
  795. const priority2 = [];
  796. let img;
  797. // all images should be paused
  798. for (var i = 0, ilen = imgs.length; i < ilen; i++) {
  799. img = imgs[i];
  800. if (scrollDirectionY === 'up') {
  801. // scrolling up
  802. if (img.top < viewableBottom && img.bottom > viewableTop - renderableBuffer) {
  803. // scrolling up, img is within viewable area
  804. // or about to be viewable area
  805. img.canRequest = img.canRender = true;
  806. priority1.push(img);
  807. continue;
  808. }
  809. if (img.bottom <= viewableTop && img.bottom > viewableTop - requestableBuffer) {
  810. // scrolling up, img is within requestable area
  811. img.canRequest = true;
  812. img.canRender = false;
  813. priority2.push(img);
  814. continue;
  815. }
  816. if (img.top >= viewableBottom && img.top < viewableBottom + renderableBuffer) {
  817. // scrolling up, img below viewable area
  818. // but it's still within renderable area
  819. // don't allow a reset
  820. img.canRequest = img.canRender = false;
  821. continue;
  822. }
  823. }
  824. else {
  825. // scrolling down
  826. if (img.bottom > viewableTop && img.top < viewableBottom + renderableBuffer) {
  827. // scrolling down, img is within viewable area
  828. // or about to be viewable area
  829. img.canRequest = img.canRender = true;
  830. priority1.push(img);
  831. continue;
  832. }
  833. if (img.top >= viewableBottom && img.top < viewableBottom + requestableBuffer) {
  834. // scrolling down, img is within requestable area
  835. img.canRequest = true;
  836. img.canRender = false;
  837. priority2.push(img);
  838. continue;
  839. }
  840. if (img.bottom <= viewableTop && img.bottom > viewableTop - renderableBuffer) {
  841. // scrolling down, img above viewable area
  842. // but it's still within renderable area
  843. // don't allow a reset
  844. img.canRequest = img.canRender = false;
  845. continue;
  846. }
  847. }
  848. img.canRequest = img.canRender = false;
  849. img.reset();
  850. }
  851. // update all imgs which are viewable
  852. priority1.sort(sortTopToBottom).forEach(i => i.update());
  853. if (scrollDirectionY === 'up') {
  854. // scrolling up
  855. priority2.sort(sortTopToBottom).reverse().forEach(i => i.update());
  856. }
  857. else {
  858. // scrolling down
  859. priority2.sort(sortTopToBottom).forEach(i => i.update());
  860. }
  861. }
  862. function sortTopToBottom(a, b) {
  863. if (a.top < b.top) {
  864. return -1;
  865. }
  866. if (a.top > b.top) {
  867. return 1;
  868. }
  869. return 0;
  870. }
  871. function parsePxUnit(val) {
  872. return (val.indexOf('px') > 0) ? parseInt(val, 10) : 0;
  873. }
  874. function cssFormat(val) {
  875. return (val > 0 ? val + 'px' : '');
  876. }
  877. //# sourceMappingURL=content.js.map