UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

342 lines (295 loc) 8.77 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2004-2011 1&1 Internet AG, Germany, http://www.1und1.de License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * Tino Butz (tbtz) ************************************************************************ */ /** * @require(qx.module.Animation) * @require(qx.module.Manipulating) * * Mixin for the {@link Scroll} container. Used when the variant * <code>qx.mobile.nativescroll</code> is set to "on". */ qx.Mixin.define("qx.ui.mobile.container.MNativeScroll", { construct() { this.addCssClass("native"); this._snapPoints = []; this.addListenerOnce("appear", this._onAppear, this); this.addListener("trackstart", this._onTrackStart, this); this.addListener("trackend", this._onTrackEnd, this); qx.bom.Event.addNativeListener( this._getContentElement(), "scroll", this._onScroll.bind(this) ); if (qx.core.Environment.get("os.name") == "ios") { this.addListener("touchmove", this._onTouchMove, this); } }, members: { _snapPoints: null, _onTrack: null, _snapTimeoutId: null, /** * Event handler for <code>appear</code> event. */ _onAppear() { this._calcSnapPoints(); }, /** * Event handler for <code>touchmove</code> event. * Needed for preventing iOS page bounce. * @param evt {qx.event.type.Touch} touchmove event. */ _onTouchMove(evt) { // If scroll container is scrollable if (this._isScrollableY()) { evt.stopPropagation(); } else { evt.preventDefault(); } }, /** * Event handler for <code>trackstart</code> events. * @param evt {qx.event.type.Track} touchmove event. */ _onTrackStart(evt) { this._onTrack = true; if (qx.core.Environment.get("os.name") == "ios") { this._preventPageBounce(); } }, /** * Prevents the iOS page bounce if scroll container reaches the upper or lower vertical scroll limit. */ _preventPageBounce() { // If scroll container is scrollable if (this._isScrollableY()) { var element = this.getContentElement(); var scrollTop = element.scrollTop; var maxScrollTop = element.scrollHeight - this.getLayoutParent().getContentElement().offsetHeight; if (scrollTop === 0) { element.scrollTop = 1; } else if (scrollTop == maxScrollTop) { element.scrollTop = maxScrollTop - 1; } } }, /** * Event handler for <code>trackend</code> events. * @param evt {qx.event.type.Track} touchmove event. */ _onTrackEnd(evt) { this._onTrack = false; if (this._snapTimeoutId) { clearTimeout(this._snapTimeoutId); } this._snapTimeoutId = setTimeout( function () { this._snap(); }.bind(this), 100 ); evt.stopPropagation(); }, /** * Event handler for <code>scroll</code> events. */ _onScroll() { var scrollLeft = this.getContentElement().scrollLeft; var scrollTop = this.getContentElement().scrollTop; this._setCurrentX(scrollLeft); this._setCurrentY(scrollTop); if (this._snapTimeoutId) { clearTimeout(this._snapTimeoutId); } this._snapTimeoutId = setTimeout( function () { if (!this._onTrack) { this._snap(); } }.bind(this), 100 ); }, /** * Calculates the snapping points for the x/y axis. */ _calcSnapPoints() { if (this._scrollProperties) { var snap = this._scrollProperties.snap; if (snap) { this._snapPoints = []; var snapTargets = this.getContentElement().querySelectorAll(snap); for (var i = 0; i < snapTargets.length; i++) { var snapPoint = qx.bom.element.Location.getRelative( this._getContentElement(), snapTargets[i], "scroll", "scroll" ); this._snapPoints.push(snapPoint); } } } }, /** * Determines the next snap points for the passed current position. * @param current {Integer} description * @param snapProperty {String} "top" or "left" * @return {Integer} the determined snap point. */ _determineSnapPoint(current, snapProperty) { for (var i = 0; i < this._snapPoints.length; i++) { var snapPoint = this._snapPoints[i]; if (current <= -snapPoint[snapProperty]) { if (i > 0) { var previousSnapPoint = this._snapPoints[i - 1]; var previousSnapDiff = Math.abs( current + previousSnapPoint[snapProperty] ); var nextSnapDiff = Math.abs(current + snapPoint[snapProperty]); if (previousSnapDiff < nextSnapDiff) { return -previousSnapPoint[snapProperty]; } else { return -snapPoint[snapProperty]; } } else { return -snapPoint[snapProperty]; } } } return current; }, /** * Snaps the scrolling area to the nearest snap point. */ _snap() { this.fireEvent("scrollEnd"); var element = this.getContentElement(); if ( element.scrollTop < 1 || element.scrollTop > this._getScrollHeight() ) { return; } var current = this._getPosition(); var nextX = this._determineSnapPoint(current[0], "left"); var nextY = this._determineSnapPoint(current[1], "top"); if (nextX != current[0] || nextY != current[1]) { this._scrollTo(nextX, nextY, 300); } }, /** * Refreshes the scroll container. Recalculates the snap points. */ _refresh() { this._calcSnapPoints(); }, /** * Mixin method. Creates the scroll element. * * @return {Element} The scroll element */ _createScrollElement() { return null; }, /** * Returns the current scroll position * @return {Array} an array with <code>[scrollLeft,scrollTop]</code>. */ _getPosition() { return [ this.getContentElement().scrollLeft, this.getContentElement().scrollTop ]; }, /** * Mixin method. Returns the scroll content element. * * @return {Element} The scroll content element */ _getScrollContentElement() { return null; }, /** * Returns the scrolling height of the inner container. * @return {Number} the scrolling height. */ _getScrollHeight() { if (!this.getContentElement()) { return 0; } return ( this.getContentElement().scrollHeight - this.getContentElement().offsetHeight ); }, /** * Returns the scrolling width of the inner container. * @return {Number} the scrolling width. */ _getScrollWidth() { if (!this.getContentElement()) { return 0; } return ( this.getContentElement().scrollWidth - this.getContentElement().offsetWidth ); }, /** * Scrolls the wrapper contents to the x/y coordinates in a given period. * * @param x {Integer} X coordinate to scroll to. * @param y {Integer} Y coordinate to scroll to. * @param time {Integer} is always <code>0</code> for this mixin. */ _scrollTo(x, y, time) { var element = this.getContentElement(); if (!time) { element.scrollLeft = x; element.scrollTop = y; return; } var startY = element.scrollTop; var startX = element.scrollLeft; if (element) { qx.bom.element.Animation.animate(element, { duration: time, keyFrames: { 0: { scrollTop: startY, scrollLeft: startX }, 100: { scrollTop: y, scrollLeft: x } }, keep: 100, timing: "ease-out" }); } } }, destruct() { qx.bom.Event.removeNativeListener( this._getContentElement(), "scroll", this._onScroll.bind(this) ); this.removeListener("touchmove", this._onTouchMove, this); this.removeListener("appear", this._onAppear, this); this.removeListener("trackstart", this._onTrackStart, this); this.removeListener("trackend", this._onTrackEnd, this); } });