mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-24 06:22:45 +08:00
chore(): begin adding ionic components to mono-repo.
This commit is contained in:
@ -0,0 +1,14 @@
|
||||
import { ScrollCallback } from '../../util/interfaces';
|
||||
|
||||
|
||||
export interface Scroll {
|
||||
enabled: boolean;
|
||||
jsScroll: boolean;
|
||||
|
||||
ionScrollStart: ScrollCallback;
|
||||
ionScroll: ScrollCallback;
|
||||
ionScrollEnd: ScrollCallback;
|
||||
|
||||
scrollToTop: (duration: number) => Promise<void>;
|
||||
scrollToBottom: (duration: number) => Promise<void>;
|
||||
}
|
32
packages/ionic-angular/src/components/scroll/scroll.scss
Normal file
32
packages/ionic-angular/src/components/scroll/scroll.scss
Normal file
@ -0,0 +1,32 @@
|
||||
@import "../../themes/ionic.globals";
|
||||
|
||||
ion-scroll {
|
||||
position: relative;
|
||||
display: block;
|
||||
}
|
||||
|
||||
ion-scroll.scroll-x .scroll-content {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
ion-scroll.scroll-y .scroll-content {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
ion-scroll[center] .scroll-content {
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
ion-scroll .scroll-content {
|
||||
@include position(0, 0, 0, 0);
|
||||
|
||||
position: absolute;
|
||||
|
||||
overflow-y: hidden;
|
||||
overflow-x: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
will-change: scroll-position;
|
||||
}
|
361
packages/ionic-angular/src/components/scroll/scroll.ts
Normal file
361
packages/ionic-angular/src/components/scroll/scroll.ts
Normal file
@ -0,0 +1,361 @@
|
||||
import { Component, Listen, Ionic, Prop } from '../index';
|
||||
import { GestureController, GestureDelegate } from '../gesture/gesture-controller';
|
||||
import { IonicGlobal, ScrollCallback, ScrollDetail } from '../../util/interfaces';
|
||||
import { Scroll as IScroll } from './scroll-interface';
|
||||
|
||||
|
||||
@Component({
|
||||
tag: 'ion-scroll'
|
||||
})
|
||||
export class Scroll implements IScroll {
|
||||
private $el: HTMLElement;
|
||||
private gesture: GestureDelegate;
|
||||
private positions: number[] = [];
|
||||
private _l: number;
|
||||
private _t: number;
|
||||
private tmr: any;
|
||||
private queued = false;
|
||||
|
||||
isScrolling: boolean = false;
|
||||
detail: ScrollDetail = {};
|
||||
|
||||
@Prop() enabled: boolean = true;
|
||||
@Prop() jsScroll: boolean = false;
|
||||
@Prop() ionScrollStart: ScrollCallback;
|
||||
@Prop() ionScroll: ScrollCallback;
|
||||
@Prop() ionScrollEnd: ScrollCallback;
|
||||
|
||||
|
||||
ionViewDidLoad() {
|
||||
if (Ionic.isServer) return;
|
||||
|
||||
const ctrl = (<IonicGlobal>Ionic).controllers.gesture = ((<IonicGlobal>Ionic).controllers.gesture || new GestureController());
|
||||
|
||||
this.gesture = ctrl.createGesture('scroll', 100, false);
|
||||
}
|
||||
|
||||
|
||||
// Native Scroll *************************
|
||||
|
||||
@Listen('scroll', { passive: true })
|
||||
onNativeScroll() {
|
||||
const self = this;
|
||||
|
||||
if (!self.queued && self.enabled) {
|
||||
self.queued = true;
|
||||
|
||||
Ionic.dom.read(function(timeStamp) {
|
||||
self.queued = false;
|
||||
self.onScroll(timeStamp || Date.now());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onScroll(timeStamp: number) {
|
||||
const self = this;
|
||||
const detail = self.detail;
|
||||
const positions = self.positions;
|
||||
|
||||
detail.timeStamp = timeStamp;
|
||||
|
||||
// get the current scrollTop
|
||||
// ******** DOM READ ****************
|
||||
detail.scrollTop = self.getTop();
|
||||
|
||||
// get the current scrollLeft
|
||||
// ******** DOM READ ****************
|
||||
detail.scrollLeft = self.getLeft();
|
||||
|
||||
if (!self.isScrolling) {
|
||||
// currently not scrolling, so this is a scroll start
|
||||
self.isScrolling = true;
|
||||
|
||||
// remember the start positions
|
||||
detail.startY = detail.scrollTop;
|
||||
detail.startX = detail.scrollLeft;
|
||||
|
||||
// new scroll, so do some resets
|
||||
detail.velocityY = detail.velocityX = detail.deltaY = detail.deltaX = positions.length = 0;
|
||||
|
||||
// emit only on the first scroll event
|
||||
if (self.ionScrollStart) {
|
||||
self.ionScrollStart(detail);
|
||||
} else {
|
||||
Ionic.emit(this, 'ionScrollStart', { detail: detail });
|
||||
}
|
||||
}
|
||||
|
||||
detail.directionX = detail.velocityDirectionX = (detail.deltaX > 0 ? 'left' : (detail.deltaX < 0 ? 'right' : null));
|
||||
detail.directionY = detail.velocityDirectionY = (detail.deltaY > 0 ? 'up' : (detail.deltaY < 0 ? 'down' : null));
|
||||
|
||||
// actively scrolling
|
||||
positions.push(detail.scrollTop, detail.scrollLeft, detail.timeStamp);
|
||||
|
||||
if (positions.length > 3) {
|
||||
// we've gotten at least 2 scroll events so far
|
||||
detail.deltaY = (detail.scrollTop - detail.startY);
|
||||
detail.deltaX = (detail.scrollLeft - detail.startX);
|
||||
|
||||
var endPos = (positions.length - 1);
|
||||
var startPos = endPos;
|
||||
var timeRange = (detail.timeStamp - 100);
|
||||
|
||||
// move pointer to position measured 100ms ago
|
||||
for (var i = endPos; i > 0 && positions[i] > timeRange; i -= 3) {
|
||||
startPos = i;
|
||||
}
|
||||
|
||||
if (startPos !== endPos) {
|
||||
// compute relative movement between these two points
|
||||
var movedTop = (positions[startPos - 2] - positions[endPos - 2]);
|
||||
var movedLeft = (positions[startPos - 1] - positions[endPos - 1]);
|
||||
var factor = 16.67 / (positions[endPos] - positions[startPos]);
|
||||
|
||||
// based on XXms compute the movement to apply for each render step
|
||||
detail.velocityY = movedTop * factor;
|
||||
detail.velocityX = movedLeft * factor;
|
||||
|
||||
// figure out which direction we're scrolling
|
||||
detail.velocityDirectionX = (movedLeft > 0 ? 'left' : (movedLeft < 0 ? 'right' : null));
|
||||
detail.velocityDirectionY = (movedTop > 0 ? 'up' : (movedTop < 0 ? 'down' : null));
|
||||
}
|
||||
}
|
||||
|
||||
clearTimeout(self.tmr);
|
||||
self.tmr = setTimeout(function() {
|
||||
|
||||
// haven't scrolled in a while, so it's a scrollend
|
||||
self.isScrolling = false;
|
||||
|
||||
Ionic.dom.read(function(timeStamp) {
|
||||
if (!self.isScrolling) {
|
||||
self.onEnd(timeStamp);
|
||||
}
|
||||
});
|
||||
}, 80);
|
||||
|
||||
// emit on each scroll event
|
||||
if (self.ionScrollStart) {
|
||||
self.ionScroll(detail);
|
||||
} else {
|
||||
Ionic.emit(this, 'ionScroll', { detail: detail });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onEnd(timeStamp: number) {
|
||||
const self = this;
|
||||
const detail = self.detail;
|
||||
|
||||
detail.timeStamp = timeStamp || Date.now();
|
||||
|
||||
// emit that the scroll has ended
|
||||
if (self.ionScrollEnd) {
|
||||
self.ionScrollEnd(detail);
|
||||
|
||||
} else {
|
||||
Ionic.emit(this, 'ionScrollEnd', { detail: detail });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enableJsScroll(contentTop: number, contentBottom: number) {
|
||||
this.jsScroll = true;
|
||||
|
||||
Ionic.listener.enable(this, 'scroll', false);
|
||||
|
||||
Ionic.listener.enable(this, 'touchstart', true);
|
||||
|
||||
contentTop; contentBottom;
|
||||
}
|
||||
|
||||
|
||||
// Touch Scroll *************************
|
||||
|
||||
@Listen('touchstart', { passive: true, enabled: false })
|
||||
onTouchStart() {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
Ionic.listener.enable(this, 'touchmove', true);
|
||||
Ionic.listener.enable(this, 'touchend', true);
|
||||
|
||||
throw 'jsScroll: TODO!';
|
||||
}
|
||||
|
||||
@Listen('touchmove', { passive: true, enabled: false })
|
||||
onTouchMove() {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Listen('touchend', { passive: true, enabled: false })
|
||||
onTouchEnd() {
|
||||
Ionic.listener.enable(this, 'touchmove', false);
|
||||
Ionic.listener.enable(this, 'touchend', false);
|
||||
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* DOM READ
|
||||
*/
|
||||
getTop() {
|
||||
if (this.jsScroll) {
|
||||
return this._t;
|
||||
}
|
||||
return this._t = this.$el.scrollTop;
|
||||
}
|
||||
|
||||
/**
|
||||
* DOM READ
|
||||
*/
|
||||
getLeft() {
|
||||
if (this.jsScroll) {
|
||||
return 0;
|
||||
}
|
||||
return this._l = this.$el.scrollLeft;
|
||||
}
|
||||
|
||||
/**
|
||||
* DOM WRITE
|
||||
*/
|
||||
setTop(top: number) {
|
||||
this._t = top;
|
||||
|
||||
if (this.jsScroll) {
|
||||
this.$el.style.transform = this.$el.style.webkitTransform = `translate3d(${this._l * -1}px,${top * -1}px,0px)`;
|
||||
|
||||
} else {
|
||||
this.$el.scrollTop = top;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DOM WRITE
|
||||
*/
|
||||
setLeft(left: number) {
|
||||
this._l = left;
|
||||
|
||||
if (this.jsScroll) {
|
||||
this.$el.style.transform = this.$el.style.webkitTransform = `translate3d(${left * -1}px,${this._t * -1}px,0px)`;
|
||||
|
||||
} else {
|
||||
this.$el.scrollLeft = left;
|
||||
}
|
||||
}
|
||||
|
||||
scrollTo(x: number, y: number, duration: number, done?: Function): Promise<any> {
|
||||
// scroll animation loop w/ easing
|
||||
// credit https://gist.github.com/dezinezync/5487119
|
||||
|
||||
let promise: Promise<any>;
|
||||
if (done === undefined) {
|
||||
// only create a promise if a done callback wasn't provided
|
||||
// done can be a null, which avoids any functions
|
||||
promise = new Promise(resolve => {
|
||||
done = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
const self = this;
|
||||
const el = self.$el;
|
||||
if (!el) {
|
||||
// invalid element
|
||||
done();
|
||||
return promise;
|
||||
}
|
||||
|
||||
if (duration < 32) {
|
||||
self.setTop(y);
|
||||
self.setLeft(x);
|
||||
done();
|
||||
return promise;
|
||||
}
|
||||
|
||||
const fromY = el.scrollTop;
|
||||
const fromX = el.scrollLeft;
|
||||
|
||||
const maxAttempts = (duration / 16) + 100;
|
||||
|
||||
let startTime: number;
|
||||
let attempts = 0;
|
||||
let stopScroll = false;
|
||||
|
||||
// scroll loop
|
||||
function step(timeStamp: number) {
|
||||
attempts++;
|
||||
|
||||
if (!self.$el || stopScroll || attempts > maxAttempts) {
|
||||
self.isScrolling = false;
|
||||
el.style.transform = el.style.webkitTransform = '';
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
let time = Math.min(1, ((timeStamp - startTime) / duration));
|
||||
|
||||
// where .5 would be 50% of time on a linear scale easedT gives a
|
||||
// fraction based on the easing method
|
||||
let easedT = (--time) * time * time + 1;
|
||||
|
||||
if (fromY !== y) {
|
||||
self.setTop((easedT * (y - fromY)) + fromY);
|
||||
}
|
||||
|
||||
if (fromX !== x) {
|
||||
self.setLeft(Math.floor((easedT * (x - fromX)) + fromX));
|
||||
}
|
||||
|
||||
if (easedT < 1) {
|
||||
// do not use DomController here
|
||||
// must use nativeRaf in order to fire in the next frame
|
||||
Ionic.dom.raf(step);
|
||||
|
||||
} else {
|
||||
stopScroll = true;
|
||||
self.isScrolling = false;
|
||||
el.style.transform = el.style.webkitTransform = '';
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
// start scroll loop
|
||||
self.isScrolling = true;
|
||||
|
||||
// chill out for a frame first
|
||||
Ionic.dom.write(() => {
|
||||
Ionic.dom.write(timeStamp => {
|
||||
startTime = timeStamp;
|
||||
step(timeStamp);
|
||||
});
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
scrollToTop(duration: number): Promise<void> {
|
||||
return this.scrollTo(0, 0, duration);
|
||||
}
|
||||
|
||||
scrollToBottom(duration: number): Promise<void> {
|
||||
let y = 0;
|
||||
if (this.$el) {
|
||||
y = this.$el.scrollHeight - this.$el.clientHeight;
|
||||
}
|
||||
return this.scrollTo(0, y, duration);
|
||||
}
|
||||
|
||||
|
||||
ionViewDidUnload() {
|
||||
this.gesture && this.gesture.destroy();
|
||||
this.gesture = this.detail = this.detail.event = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,29 @@
|
||||
import { Component, CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { IonicApp, IonicModule } from '../../../..';
|
||||
|
||||
|
||||
@Component({
|
||||
templateUrl: 'main.html'
|
||||
})
|
||||
export class AppComponent {
|
||||
doRefresh() {
|
||||
console.log('DOREFRESH');
|
||||
}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
IonicModule.forRoot(AppComponent)
|
||||
],
|
||||
bootstrap: [IonicApp],
|
||||
entryComponents: [
|
||||
AppComponent
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class AppModule {}
|
@ -0,0 +1,66 @@
|
||||
<ion-header>
|
||||
|
||||
<ion-toolbar>
|
||||
<ion-title>Scroll</ion-title>
|
||||
</ion-toolbar>
|
||||
|
||||
</ion-header>
|
||||
|
||||
|
||||
<ion-content>
|
||||
|
||||
<h2>Horizontal</h2>
|
||||
<ion-scroll scrollX="true" style="height: 200px">
|
||||
<div style="height: 200px; width: 2500px;" class="pattern1"></div>
|
||||
</ion-scroll>
|
||||
|
||||
<h2>Vertical</h2>
|
||||
<ion-scroll scrollY="true" style="width: 200px; height: 500px">
|
||||
<div style="height: 2500px; width: 200px;" class="pattern2"></div>
|
||||
</ion-scroll>
|
||||
|
||||
<h2>Both</h2>
|
||||
<ion-scroll scrollX scrollY style="width: 100%; height: 500px">
|
||||
<div style="height: 2500px; width: 2500px;" class="pattern3"></div>
|
||||
</ion-scroll>
|
||||
|
||||
</ion-content>
|
||||
|
||||
|
||||
<style>
|
||||
f { display: block; height: 400px; width: 100%; background-color: #387ef5; margin-bottom: 15px; }
|
||||
#counter {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.pattern1 {
|
||||
background:
|
||||
radial-gradient(circle at 0% 50%, rgba(96, 16, 48, 0) 9px, #613 10px, rgba(96, 16, 48, 0) 11px) 0 10px,
|
||||
radial-gradient(at 100% 100%, rgba(96, 16, 48, 0) 9px, #613 10px, rgba(96, 16, 48, 0) 11px),
|
||||
#8a3;
|
||||
background-size: 20px 20px;
|
||||
}
|
||||
|
||||
.pattern2 {
|
||||
background:
|
||||
linear-gradient(63deg, #999 23%, transparent 23%) 7px 0,
|
||||
linear-gradient(63deg, transparent 74%, #999 78%),
|
||||
linear-gradient(63deg, transparent 34%, #999 38%, #999 58%, transparent 62%),
|
||||
#444;
|
||||
background-size: 16px 48px;
|
||||
}
|
||||
|
||||
.pattern3 {
|
||||
background:
|
||||
linear-gradient(135deg, #708090 22px, #d9ecff 22px, #d9ecff 24px, transparent 24px, transparent 67px, #d9ecff 67px, #d9ecff 69px, transparent 69px),
|
||||
linear-gradient(225deg, #708090 22px, #d9ecff 22px, #d9ecff 24px, transparent 24px, transparent 67px, #d9ecff 67px, #d9ecff 69px, transparent 69px)0 64px;
|
||||
background-color:#708090;
|
||||
background-size: 64px 128px
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user