├── .editorconfig ├── .gitignore ├── README.md ├── config.xml ├── ionic.config.json ├── package.json ├── readme ├── shrink-0.5.gif ├── shrink-0.5.mov ├── shrink-1.gif ├── shrink-1.mov ├── static.gif ├── static.mov ├── translate.gif └── translate.mov ├── resources ├── android │ ├── icon │ │ ├── drawable-hdpi-icon.png │ │ ├── drawable-ldpi-icon.png │ │ ├── drawable-mdpi-icon.png │ │ ├── drawable-xhdpi-icon.png │ │ ├── drawable-xxhdpi-icon.png │ │ └── drawable-xxxhdpi-icon.png │ └── splash │ │ ├── drawable-land-hdpi-screen.png │ │ ├── drawable-land-ldpi-screen.png │ │ ├── drawable-land-mdpi-screen.png │ │ ├── drawable-land-xhdpi-screen.png │ │ ├── drawable-land-xxhdpi-screen.png │ │ ├── drawable-land-xxxhdpi-screen.png │ │ ├── drawable-port-hdpi-screen.png │ │ ├── drawable-port-ldpi-screen.png │ │ ├── drawable-port-mdpi-screen.png │ │ ├── drawable-port-xhdpi-screen.png │ │ ├── drawable-port-xxhdpi-screen.png │ │ └── drawable-port-xxxhdpi-screen.png ├── icon.png ├── ios │ ├── icon │ │ ├── icon-40.png │ │ ├── icon-40@2x.png │ │ ├── icon-40@3x.png │ │ ├── icon-50.png │ │ ├── icon-50@2x.png │ │ ├── icon-60.png │ │ ├── icon-60@2x.png │ │ ├── icon-60@3x.png │ │ ├── icon-72.png │ │ ├── icon-72@2x.png │ │ ├── icon-76.png │ │ ├── icon-76@2x.png │ │ ├── icon-83.5@2x.png │ │ ├── icon-small.png │ │ ├── icon-small@2x.png │ │ ├── icon-small@3x.png │ │ ├── icon.png │ │ └── icon@2x.png │ └── splash │ │ ├── Default-568h@2x~iphone.png │ │ ├── Default-667h.png │ │ ├── Default-736h.png │ │ ├── Default-Landscape-736h.png │ │ ├── Default-Landscape@2x~ipad.png │ │ ├── Default-Landscape@~ipadpro.png │ │ ├── Default-Landscape~ipad.png │ │ ├── Default-Portrait@2x~ipad.png │ │ ├── Default-Portrait@~ipadpro.png │ │ ├── Default-Portrait~ipad.png │ │ ├── Default@2x~iphone.png │ │ └── Default~iphone.png └── splash.png ├── src ├── app │ ├── app.component.ts │ ├── app.html │ ├── app.module.ts │ ├── app.scss │ └── main.ts ├── assets │ └── icon │ │ └── favicon.ico ├── components │ └── scroll-hide │ │ ├── dev │ │ ├── _scroll-hide.ts │ │ ├── scroll-hide-v1.ts │ │ ├── scroll-hide-v2.0.ts │ │ ├── scroll-hide-v2.1.ts │ │ ├── scroll-hide-v2.2.ts │ │ ├── scroll-hide-v3.0.ts │ │ └── scroll-hide-v3.1.ts │ │ └── scroll-hide.ts ├── index.html ├── manifest.json ├── pages │ ├── about │ │ ├── about.html │ │ ├── about.scss │ │ └── about.ts │ ├── contact │ │ ├── contact.html │ │ ├── contact.scss │ │ └── contact.ts │ ├── home │ │ ├── home.html │ │ ├── home.scss │ │ └── home.ts │ └── tabs │ │ ├── tabs.html │ │ └── tabs.ts ├── service-worker.js └── theme │ └── variables.scss ├── tsconfig.json ├── tslint.json ├── typings.json └── typings ├── globals └── jquery │ ├── index.d.ts │ └── typings.json └── index.d.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent coding styles between different editors and IDEs 2 | # editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | 10 | # We recommend you to keep these unchanged 11 | end_of_line = lf 12 | charset = utf-8 13 | trim_trailing_whitespace = true 14 | insert_final_newline = true 15 | 16 | [*.md] 17 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Specifies intentionally untracked files to ignore when using Git 2 | # http://git-scm.com/docs/gitignore 3 | 4 | *~ 5 | *.sw[mnpcod] 6 | *.log 7 | *.tmp 8 | *.tmp.* 9 | log.txt 10 | *.sublime-project 11 | *.sublime-workspace 12 | .vscode/ 13 | npm-debug.log* 14 | 15 | .idea/ 16 | .sass-cache/ 17 | .tmp/ 18 | .versions/ 19 | coverage/ 20 | dist/ 21 | node_modules/ 22 | tmp/ 23 | temp/ 24 | hooks/ 25 | platforms/ 26 | plugins/ 27 | plugins/android.json 28 | plugins/ios.json 29 | www/ 30 | $RECYCLE.BIN/ 31 | 32 | .DS_Store 33 | Thumbs.db 34 | UserInterfaceState.xcuserstate 35 | 36 | src/components/scroll-hide/dev 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ionic Scroll Hide 2 | 3 | This plugin was inspired by [Ionic Scroll Sista](https://github.com/djett41/ionic-scroll-sista). Tested in Ionic 2 and 3. 4 | 5 | ## Setup 6 | 7 | 1. Install jQuery library 8 | ``` 9 | npm install --save jquery 10 | ``` 11 | 2. Copy `scroll-hide` folder into your Ionic project (src/components). 12 | 3. When using Ionic 2, remember to uncomment the line 547 and comment the line 545 (inside the `onContentScroll()` of `ScrollHide`). 13 | ```typescript 14 | export class ScrollHide implements OnInit, OnDestroy { 15 | ... 16 | onContentScroll(event: ScrollEvent, isMoveEnd: boolean) { 17 | 18 | // For Ionic 3 19 | //let maxScrollTop = this.content._scrollContent.nativeElement.scrollHeight - (event.contentTop + event.contentHeight + event.contentBottom) - (this.extendBottom ? this.footerHeight : 0); 20 | // For Ionic 2 21 | let maxScrollTop = this.content._scrollEle.scrollHeight - (event.contentTop + event.contentHeight + event.contentBottom) - (this.extendBottom ? this.footerHeight : 0); // For ionic2 22 | ... 23 | } 24 | ... 25 | } 26 | ``` 27 | 4. Add `ScrollHide` class to the declarations NgModule metadata. 28 | #### app.module.ts 29 | ```typescript 30 | import { ScrollHide } from './../components/scroll-hide/scroll-hide'; 31 | 32 | @NgModule({ 33 | declarations: [ 34 | MyApp, 35 | ... 36 | ScrollHide, 37 | ], 38 | ... 39 | }) 40 | export class AppModule { } 41 | ``` 42 | 43 | ## Usage 44 | 45 | Scroll Hide is an attribute directive, and requires an input of `viewCtrl`. So, rememeber to inject ViewController into the page constrcutor. To enable the plugin, add `[scroll-hide]="viewCtrl"` into `ion-content`. 46 | 47 | 48 | There are three transistion types can be used for each `ion-toolbar`, they are `Static`, `Translate`, `Shrink`. 49 | 50 | #### Static 51 | The toolbar would not be translated and shrinked during scrolling. It is the default type. 52 | 53 | ![Static](readme/static.gif) 54 | 55 | #### Translate 56 | The toolbar would be translated during scrolling. To enable, add an attribute of `scroll-hide-translate` into ion-toolbar. 57 | 58 | ![Translate](readme/translate.gif) 59 | 60 | #### Shrink 61 | The toolbar would be shrinked during scrolling. To enable, add an attribute of `scroll-hide-shrink="shrinkVal"` into ion-toolbar, where `shrinkVal` is the maximum percentage of shrinking, defualt to 1. 62 | 63 | |shrinkVal = 1 | shrinkVal = 0.5 | 64 | ------------------------------------|---------------------------------------| 65 | |![Shrink 1](readme/shrink-1.gif) | ![Shrink 0.5](readme/shrink-0.5.gif) | 66 | 67 | 68 | ## Example 69 | 70 | #### home.ts 71 | ```typescript 72 | import { Component } from '@angular/core'; 73 | import { NavController, ViewController } from 'ionic-angular'; 74 | 75 | @Component({ 76 | selector: 'page-home', 77 | templateUrl: 'home.html' 78 | }) 79 | export class HomePage { 80 | 81 | constructor(public navCtrl: NavController, 82 | public viewCtrl: ViewController) {} 83 | } 84 | ``` 85 | #### home.html 86 | ```html 87 | 88 | 89 | Home 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 |

Welcome to Ionic!

99 |
100 | ``` -------------------------------------------------------------------------------- /config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | scroll-hide-test 4 | An awesome Ionic/Cordova app. 5 | Ionic Framework Team 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /ionic.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scroll-hide", 3 | "app_id": "", 4 | "v2": true, 5 | "typescript": true 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scroll-hide-test", 3 | "version": "0.0.0", 4 | "author": "Ionic Framework", 5 | "homepage": "http://ionicframework.com/", 6 | "private": true, 7 | "scripts": { 8 | "clean": "ionic-app-scripts clean", 9 | "build": "ionic-app-scripts build", 10 | "lint": "ionic-app-scripts lint", 11 | "ionic:build": "ionic-app-scripts build", 12 | "ionic:serve": "ionic-app-scripts serve" 13 | }, 14 | "dependencies": { 15 | "@angular/common": "4.1.2", 16 | "@angular/compiler": "4.1.2", 17 | "@angular/compiler-cli": "4.1.2", 18 | "@angular/core": "4.1.2", 19 | "@angular/forms": "4.1.2", 20 | "@angular/http": "4.1.2", 21 | "@angular/platform-browser": "4.1.2", 22 | "@angular/platform-browser-dynamic": "4.1.2", 23 | "@ionic-native/core": "3.10.2", 24 | "@ionic-native/splash-screen": "3.10.2", 25 | "@ionic-native/status-bar": "3.10.2", 26 | "@ionic/storage": "2.0.1", 27 | "ionic-angular": "3.3.0", 28 | "ionicons": "3.0.0", 29 | "jquery": "^3.2.1", 30 | "rxjs": "5.1.1", 31 | "sw-toolbox": "3.6.0", 32 | "zone.js": "0.8.11" 33 | }, 34 | "devDependencies": { 35 | "@ionic/app-scripts": "1.3.7", 36 | "typescript": "2.3.3" 37 | }, 38 | "cordovaPlugins": [ 39 | "cordova-plugin-statusbar", 40 | "cordova-plugin-device", 41 | "cordova-plugin-whitelist", 42 | "cordova-plugin-splashscreen", 43 | "cordova-plugin-console", 44 | "ionic-plugin-keyboard" 45 | ], 46 | "cordovaPlatforms": [ 47 | "ios", 48 | { 49 | "platform": "ios", 50 | "version": "", 51 | "locator": "ios" 52 | } 53 | ], 54 | "description": "scroll-hide-test: An Ionic project" 55 | } 56 | -------------------------------------------------------------------------------- /readme/shrink-0.5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/readme/shrink-0.5.gif -------------------------------------------------------------------------------- /readme/shrink-0.5.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/readme/shrink-0.5.mov -------------------------------------------------------------------------------- /readme/shrink-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/readme/shrink-1.gif -------------------------------------------------------------------------------- /readme/shrink-1.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/readme/shrink-1.mov -------------------------------------------------------------------------------- /readme/static.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/readme/static.gif -------------------------------------------------------------------------------- /readme/static.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/readme/static.mov -------------------------------------------------------------------------------- /readme/translate.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/readme/translate.gif -------------------------------------------------------------------------------- /readme/translate.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/readme/translate.mov -------------------------------------------------------------------------------- /resources/android/icon/drawable-hdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/android/icon/drawable-hdpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-ldpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/android/icon/drawable-ldpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-mdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/android/icon/drawable-mdpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-xhdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/android/icon/drawable-xhdpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-xxhdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/android/icon/drawable-xxhdpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-xxxhdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/android/icon/drawable-xxxhdpi-icon.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-hdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/android/splash/drawable-land-hdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-ldpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/android/splash/drawable-land-ldpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-mdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/android/splash/drawable-land-mdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-xhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/android/splash/drawable-land-xhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-xxhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/android/splash/drawable-land-xxhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-xxxhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/android/splash/drawable-land-xxxhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-hdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/android/splash/drawable-port-hdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-ldpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/android/splash/drawable-port-ldpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-mdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/android/splash/drawable-port-mdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-xhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/android/splash/drawable-port-xhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-xxhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/android/splash/drawable-port-xxhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-xxxhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/android/splash/drawable-port-xxxhdpi-screen.png -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/icon.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/icon/icon-40.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/icon/icon-40@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/icon/icon-40@3x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/icon/icon-50.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/icon/icon-50@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/icon/icon-60.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/icon/icon-60@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/icon/icon-60@3x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/icon/icon-72.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/icon/icon-72@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/icon/icon-76.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/icon/icon-76@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/icon/icon-83.5@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/icon/icon-small.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/icon/icon-small@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/icon/icon-small@3x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/icon/icon.png -------------------------------------------------------------------------------- /resources/ios/icon/icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/icon/icon@2x.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-568h@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/splash/Default-568h@2x~iphone.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-667h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/splash/Default-667h.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-736h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/splash/Default-736h.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Landscape-736h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/splash/Default-Landscape-736h.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Landscape@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/splash/Default-Landscape@2x~ipad.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Landscape@~ipadpro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/splash/Default-Landscape@~ipadpro.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Landscape~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/splash/Default-Landscape~ipad.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Portrait@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/splash/Default-Portrait@2x~ipad.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Portrait@~ipadpro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/splash/Default-Portrait@~ipadpro.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Portrait~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/splash/Default-Portrait~ipad.png -------------------------------------------------------------------------------- /resources/ios/splash/Default@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/splash/Default@2x~iphone.png -------------------------------------------------------------------------------- /resources/ios/splash/Default~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/ios/splash/Default~iphone.png -------------------------------------------------------------------------------- /resources/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/resources/splash.png -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Platform } from 'ionic-angular'; 3 | import { StatusBar } from '@ionic-native/status-bar'; 4 | import { SplashScreen } from '@ionic-native/splash-screen'; 5 | 6 | import { TabsPage } from '../pages/tabs/tabs'; 7 | 8 | @Component({ 9 | templateUrl: 'app.html' 10 | }) 11 | export class MyApp { 12 | rootPage:any = TabsPage; 13 | 14 | constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen) { 15 | platform.ready().then(() => { 16 | // Okay, so the platform is ready and our plugins are available. 17 | // Here you can do any higher level native things you might need. 18 | statusBar.styleDefault(); 19 | splashScreen.hide(); 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/app/app.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, ErrorHandler } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular'; 4 | import { MyApp } from './app.component'; 5 | 6 | import { AboutPage } from '../pages/about/about'; 7 | import { ContactPage } from '../pages/contact/contact'; 8 | import { HomePage } from '../pages/home/home'; 9 | import { TabsPage } from '../pages/tabs/tabs'; 10 | 11 | import { StatusBar } from '@ionic-native/status-bar'; 12 | import { SplashScreen } from '@ionic-native/splash-screen'; 13 | 14 | 15 | // Directive 16 | import { ScrollHide } from './../components/scroll-hide/scroll-hide'; 17 | 18 | 19 | 20 | 21 | @NgModule({ 22 | declarations: [ 23 | MyApp, 24 | AboutPage, 25 | ContactPage, 26 | HomePage, 27 | TabsPage, 28 | 29 | // Directive 30 | ScrollHide, 31 | 32 | ], 33 | imports: [ 34 | BrowserModule, 35 | IonicModule.forRoot(MyApp) 36 | ], 37 | bootstrap: [IonicApp], 38 | entryComponents: [ 39 | MyApp, 40 | AboutPage, 41 | ContactPage, 42 | HomePage, 43 | TabsPage 44 | ], 45 | providers: [ 46 | StatusBar, 47 | SplashScreen, 48 | { provide: ErrorHandler, useClass: IonicErrorHandler } 49 | ] 50 | }) 51 | export class AppModule { } 52 | -------------------------------------------------------------------------------- /src/app/app.scss: -------------------------------------------------------------------------------- 1 | // http://ionicframework.com/docs/v2/theming/ 2 | 3 | 4 | // App Global Sass 5 | // -------------------------------------------------- 6 | // Put style rules here that you want to apply globally. These 7 | // styles are for the entire app and not just one component. 8 | // Additionally, this file can be also used as an entry point 9 | // to import other Sass files to be included in the output CSS. 10 | // 11 | // Shared Sass variables, which can be used to adjust Ionic's 12 | // default Sass variables, belong in "theme/variables.scss". 13 | // 14 | // To declare rules for a specific mode, create a child rule 15 | // for the .md, .ios, or .wp mode classes. The mode class is 16 | // automatically applied to the element in the app. 17 | -------------------------------------------------------------------------------- /src/app/main.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | 3 | import { AppModule } from './app.module'; 4 | 5 | platformBrowserDynamic().bootstrapModule(AppModule); 6 | -------------------------------------------------------------------------------- /src/assets/icon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaslee2775/ionic-scroll-hide/ecaf85b85621f3d7de00082140ebc35e111e8793/src/assets/icon/favicon.ico -------------------------------------------------------------------------------- /src/components/scroll-hide/dev/_scroll-hide.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Directive, 3 | ElementRef, NgZone, HostListener, Input, Renderer, 4 | OnInit, OnDestroy, 5 | } from '@angular/core'; 6 | 7 | import { Content, ScrollEvent, NavController, } from 'ionic-angular' 8 | 9 | 10 | import * as $ from 'jquery' 11 | 12 | 13 | 14 | 15 | 16 | type DomWrite = (fn: (timeStamp?: number) => void, ctx?: any) => void; 17 | 18 | 19 | function constrain(val: number, min: number, max: number) { 20 | return Math.min(max, Math.max(val, min)); 21 | } 22 | 23 | function forEach(children: HTMLCollection, callback: (ele: HTMLElement) => void) { 24 | for(var i = 0; i < children.length; i++) { 25 | callback( children[i]); 26 | } 27 | } 28 | 29 | 30 | 31 | interface Item { 32 | ele: HTMLElement; 33 | startY: number; 34 | endY: number; 35 | unshrinkable: boolean; 36 | } 37 | 38 | 39 | @Directive({ 40 | selector: '[scroll-hide]' // Attribute selector 41 | }) 42 | export class ScrollHide implements OnInit, OnDestroy { 43 | 44 | @Input() navCtrl: NavController; 45 | 46 | initiated: boolean = false; 47 | 48 | contentTop: number; 49 | endY: number; 50 | 51 | //tabbarOnBottom: boolean = true; 52 | //tabbarEle: HTMLElement; 53 | headerItems: Item[] = []; 54 | footerItems: Item[] = []; 55 | 56 | 57 | constructor(private content: Content, 58 | private renderer: Renderer, 59 | private zone: NgZone) {} 60 | 61 | 62 | ngOnInit() { 63 | this.content.fullscreen = true; 64 | 65 | //console.log("mode:", this.content._mode, "tabs:", this.content._tabs); 66 | console.log("ngOnInit() > navCtrl:", this.navCtrl) 67 | 68 | 69 | /* 70 | this.navCtrl.viewDidEnter.subscribe((event) => { 71 | //console.log("viewDidEnter > _tabsPlacement:", this.content._tabsPlacement); 72 | console.log("ScrollHide > viewDidEnter()", event); 73 | }); 74 | this.navCtrl.viewWillLeave.subscribe((event) => { 75 | console.log("ScrollHide > viewWillLeave()", event); 76 | }); 77 | */ 78 | 79 | 80 | 81 | let win: any = window; 82 | win.temp = win.temp || {}; 83 | win.temp.content = this.content; 84 | win.temp.zone = this.zone; 85 | } 86 | 87 | ngOnDestroy() { 88 | console.log("ngOnDestroy()"); 89 | } 90 | 91 | private init() { 92 | let contentEle: HTMLElement = this.content.getNativeElement(); 93 | let headerEle = contentEle.parentElement.querySelector("ion-header"); 94 | let footerEle = contentEle.parentElement.querySelector("ion-footer"); 95 | if(headerEle) { 96 | this.headerItems = []; 97 | for(var i = 0; i < headerEle.children.length; i++) { 98 | this.headerItems[i] = { 99 | ele: headerEle.children[i], 100 | startY: 0, 101 | endY: 0, 102 | unshrinkable: false 103 | }; 104 | } 105 | } 106 | if(footerEle) { 107 | this.footerItems = []; 108 | for(var i = 0; i < footerEle.children.length; i++) { 109 | this.footerItems[i] = { 110 | ele: footerEle.children[i], 111 | startY: 0, 112 | endY: 0, 113 | unshrinkable: false 114 | }; 115 | } 116 | } 117 | 118 | 119 | 120 | 121 | let hasTabbar = (this.content._tabs !== null); 122 | if(hasTabbar) { 123 | //this.tabbarEle = this.content._tabs._tabbar.nativeElement; 124 | let tabbarEle = ( this.content._tabs)._tabbar.nativeElement; 125 | 126 | if(this.content._tabsPlacement === "bottom") { 127 | this.footerItems.push({ 128 | ele: tabbarEle, 129 | startY: 0, 130 | endY: 0, 131 | unshrinkable: false 132 | }); 133 | } else { 134 | this.headerItems.push({ 135 | ele: tabbarEle, 136 | startY: 0, 137 | endY: 0, 138 | unshrinkable: false 139 | }); 140 | } 141 | } 142 | 143 | 144 | this.headerItems = this.headerItems.reverse(); 145 | this.footerItems = this.footerItems.reverse(); 146 | 147 | 148 | 149 | var contentTop = 0; 150 | 151 | console.group("headerItems") 152 | 153 | var shrinkableHeightSum = 0; 154 | this.headerItems.forEach((item, index) => { 155 | contentTop += item.ele.offsetHeight; 156 | 157 | //this.renderer.setElementStyle(item.ele, "z-index", this.headerItems.length - index + ""); 158 | 159 | if(item.ele.classList.contains("tabbar")) { 160 | this.renderer.setElementStyle(item.ele, "overflow", "hidden"); 161 | forEach(item.ele.children, (tabButton) => { 162 | this.renderer.setElementStyle(tabButton, "align-self", "flex-end"); 163 | }); 164 | } else { 165 | this.renderer.setElementStyle(item.ele, "z-index", (index + 1) + ""); 166 | } 167 | 168 | if(item.ele.tagName === "ION-NAVBAR") { 169 | this.renderer.setElementStyle(item.ele, "position", "relative") 170 | } 171 | 172 | 173 | if(!item.ele.classList.contains("unshrinkable")) { 174 | item.startY = shrinkableHeightSum; 175 | item.endY = shrinkableHeightSum + item.ele.offsetHeight; 176 | item.unshrinkable = false; 177 | 178 | shrinkableHeightSum += item.ele.offsetHeight; 179 | 180 | } else { 181 | item.startY = shrinkableHeightSum; 182 | item.endY = shrinkableHeightSum; 183 | item.unshrinkable = true; 184 | } 185 | 186 | console.log(item, item.ele.offsetHeight); 187 | }); 188 | this.contentTop = contentTop; 189 | this.defaultEnd = this.contentTop * 2; 190 | this.endY = shrinkableHeightSum; 191 | 192 | console.groupEnd(); 193 | 194 | 195 | console.group("footerItems") 196 | this.footerItems.forEach(item => { 197 | console.log(item.ele, item.ele.offsetHeight); 198 | }); 199 | console.groupEnd(); 200 | 201 | console.log("contentTop:", contentTop, ", endY:", this.endY); 202 | 203 | 204 | /* 205 | ion-header { 206 | pointer-events: none; 207 | } 208 | 209 | ion-header > * { 210 | pointer-events: all; 211 | }*/ 212 | 213 | if(headerEle) { 214 | this.renderer.setElementStyle(headerEle, "pointer-events", "none"); 215 | this.headerItems.forEach((item) => { 216 | this.renderer.setElementStyle(item.ele, "pointer-events", "all"); 217 | }); 218 | } 219 | if(footerEle) { 220 | this.renderer.setElementStyle(footerEle, "pointer-events", "none"); 221 | this.footerItems.forEach((item) => { 222 | this.renderer.setElementStyle(item.ele, "pointer-events", "all"); 223 | }); 224 | } 225 | } 226 | 227 | 228 | defaultDelay: number = 400 * 2; 229 | defaultDuration: number = 400; 230 | 231 | defaultEnd: number = 0; 232 | y: number = 0; 233 | yPrev: number = 0; 234 | scrollTopPrev: number = 0; 235 | 236 | @HostListener("ionScroll", ["$event"]) 237 | onContentScoll(event: ScrollEvent) { 238 | //console.log("ionScroll", event); //{ contentHeight: event.contentHeight, scrollHeight: event.scrollHeight }); 239 | //this.checkTabsPlacment(); 240 | 241 | if(!this.initiated) { 242 | this.initiated = true; 243 | this.init(); 244 | this.content.resize(); 245 | } 246 | 247 | 248 | let maxScrollTop = event.scrollHeight - (event.contentTop + event.contentHeight + event.contentBottom); 249 | 250 | 251 | 252 | var duration = 0; 253 | let scrollTop = event.scrollTop; 254 | let scrollTopDiff = scrollTop - this.scrollTopPrev; 255 | 256 | let y = (scrollTop >= 0) ? constrain(this.yPrev + scrollTopDiff, 0, this.defaultEnd) : 0; 257 | 258 | 259 | 260 | //if we are at the bottom, animate the header/tabs back in 261 | //if (scrollView.getScrollMax().top - scrollTop <= contentTop) { 262 | // if (scrollTop >= maxScrollTop) { 263 | // y = 0; 264 | // duration = this.defaultDuration; 265 | // } 266 | 267 | this.scrollTopPrev = scrollTop; 268 | 269 | //if previous and current y are the same, no need to continue 270 | if (this.yPrev === y) { 271 | return; 272 | } 273 | this.yPrev = y; 274 | 275 | 276 | 277 | //console.log({ y: y, scrollTop: event.scrollTop, maxScrollTop: maxScrollTop}); 278 | 279 | 280 | this.modifyDom(y * 1.0, duration, event.domWrite); 281 | } 282 | 283 | 284 | private modifyDom(y: number, duration: number, dowWrite: DomWrite) { 285 | dowWrite(() => { 286 | 287 | // Header Items 288 | this.headerItems.forEach((item) => { 289 | 290 | /*if(y < item.endY) { 291 | this.translateElementY(item.ele, -y, duration); 292 | 293 | let dy = (y - item.startY); 294 | let fadeAmt = constrain(1 - (dy / item.ele.offsetHeight * 2), 0, 1); 295 | 296 | forEach(item.ele.children, (navbarChild) => { 297 | if(!navbarChild.classList.contains("toolbar-background")) { 298 | this.renderer.setElementStyle(navbarChild, "opacity", fadeAmt + ""); 299 | } 300 | }); 301 | 302 | } else { 303 | this.translateElementY(item.ele, -item.endY, duration); 304 | // if(item.unshrinkable) { 305 | // this.translateElementY(item.ele, -item.endY, duration); 306 | // } else { 307 | // this.translateElementY(item.ele, -item.endY - 100, duration); 308 | // } 309 | }*/ 310 | 311 | 312 | /* 313 | if(y <= this.endY) { 314 | if(item.startY <= y) { 315 | this.translateElementY(item.ele, -(y - item.startY), duration); 316 | 317 | if(!item.unshrinkable) { 318 | let dy = (y - item.startY); 319 | let fadeAmt = constrain(1 - (dy / item.ele.offsetHeight), 0, 1); 320 | 321 | forEach(item.ele.children, (navbarChild) => { 322 | if(!navbarChild.classList.contains("toolbar-background")) { 323 | this.renderer.setElementStyle(navbarChild, "opacity", fadeAmt + ""); 324 | } 325 | }); 326 | } 327 | 328 | } else { 329 | this.translateElementY(item.ele, 0, duration); 330 | } 331 | } else { 332 | this.translateElementY(item.ele, -(this.endY - item.startY), duration); 333 | }*/ 334 | 335 | 336 | 337 | let dy = constrain((y - item.startY), 0, (this.endY - item.startY)); 338 | 339 | if(item.ele.classList.contains("tabbar")) { 340 | var val = constrain((dy / this.content._tabbarHeight), 0, 1); 341 | 342 | console.log("val:", val); 343 | 344 | let height = this.content._tabbarHeight * (1 - val); 345 | if(height >= 0) { 346 | this.renderer.setElementStyle(item.ele, "height", height + "px"); 347 | this.renderer.setElementStyle(item.ele, "display", ""); 348 | } else { 349 | this.renderer.setElementStyle(item.ele, "display", "none"); 350 | } 351 | 352 | } else { 353 | var val = constrain((dy / item.ele.offsetHeight), 0, 1); 354 | 355 | this.translateElementY(item.ele, -dy, duration); 356 | } 357 | if(!item.unshrinkable) { 358 | //let fadeAmt = constrain(1 - (dy / item.ele.offsetHeight), 0, 1); 359 | let fadeAmt = 1 - val; 360 | forEach(item.ele.children, (navbarChild) => { 361 | if(!navbarChild.classList.contains("toolbar-background")) { 362 | this.renderer.setElementStyle(navbarChild, "opacity", fadeAmt + ""); 363 | } 364 | }); 365 | //item.ele.querySelectorAll("") 366 | } 367 | 368 | 369 | 370 | 371 | 372 | //this.translateElementY(item.ele, -y, duration); 373 | 374 | // if(item.ele.tagName === "ION-NAVBAR") { 375 | // var fadeAmt = 1 - (y / item.ele.offsetHeight); 376 | 377 | // forEach(item.ele.children, (navbarChild) => { 378 | // if(!navbarChild.classList.contains("toolbar-background")) { 379 | // this.renderer.setElementStyle(navbarChild, "opacity", fadeAmt + ""); 380 | // //if (scaleHeaderElements) { 381 | // // this.renderer.setElementStyle(navbarChild, "transform", `scale(${fadeAmt}, ${fadeAmt})`); 382 | // //} 383 | // } 384 | // }); 385 | // } 386 | }); 387 | 388 | 389 | 390 | // Footer Items 391 | this.footerItems.forEach((item) => { 392 | this.translateElementY(item.ele, y, duration); 393 | }); 394 | }); 395 | } 396 | 397 | private translateElementY(ele: HTMLElement, y: number, duration: number) { 398 | this.renderer.setElementStyle(ele, "webkitTransform", `translate3d(0, ${y}px,0)`); 399 | 400 | if (duration && !ele.style.transitionDuration) { 401 | ele.style.transitionDuration = duration + "ms"; 402 | setTimeout(() => { 403 | ele.style.transitionDuration = ""; 404 | }, this.defaultDelay); 405 | } 406 | } 407 | 408 | private translateElementHeight(ele: HTMLElement, height: number) { 409 | this.renderer.setElementStyle(ele, "height", `${height}px`); 410 | } 411 | } 412 | -------------------------------------------------------------------------------- /src/components/scroll-hide/dev/scroll-hide-v1.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Directive, 3 | ElementRef, NgZone, HostListener, Input, Renderer, 4 | OnInit, OnDestroy, 5 | } from '@angular/core'; 6 | 7 | import { Content, ScrollEvent, NavController, ViewController } from 'ionic-angular' 8 | 9 | 10 | import * as $ from 'jquery' 11 | 12 | 13 | 14 | 15 | 16 | type DomWrite = (fn: (timeStamp?: number) => void, ctx?: any) => void; 17 | 18 | 19 | function constrain(val: number, min: number, max: number) { 20 | return Math.min(max, Math.max(val, min)); 21 | } 22 | 23 | function forEach(children: HTMLCollection, callback: (ele: HTMLElement) => void) { 24 | for(var i = 0; i < children.length; i++) { 25 | callback( children[i]); 26 | } 27 | } 28 | 29 | 30 | /* 31 | interface Item { 32 | ele: HTMLElement; 33 | startY: number; 34 | endY: number; 35 | unshrinkable: boolean; 36 | } 37 | */ 38 | 39 | enum ItemType { 40 | Navbar, Toolbar, Tabbar 41 | } 42 | class Item { 43 | public type: ItemType; 44 | public shrinkable: boolean; 45 | public startY = 0; 46 | public endY = 0; 47 | 48 | constructor(public ele: HTMLElement) { 49 | 50 | if(ele.classList.contains("tabbar")) { 51 | this.type = ItemType.Tabbar; 52 | } else if(ele.tagName === "ION-NAVBAR") { 53 | this.type = ItemType.Navbar; 54 | } else { 55 | this.type = ItemType.Toolbar; 56 | } 57 | 58 | this.shrinkable = !ele.classList.contains("unshrinkable"); 59 | } 60 | 61 | setY(startY: number, endY: number) { 62 | this.startY = startY; 63 | this.endY = endY; 64 | } 65 | } 66 | 67 | 68 | @Directive({ 69 | selector: '[scroll-hide]' // Attribute selector 70 | }) 71 | export class ScrollHide implements OnInit, OnDestroy { 72 | 73 | @Input() navCtrl: NavController; 74 | @Input() viewCtrl: ViewController; 75 | 76 | initiated: boolean = false; 77 | 78 | contentTop: number; 79 | endY: number; 80 | 81 | //tabbarOnBottom: boolean = true; 82 | //tabbarEle: HTMLElement; 83 | headerItems: Item[] = []; 84 | footerItems: Item[] = []; 85 | 86 | headerEle: HTMLElement; 87 | footerEle: HTMLElement; 88 | 89 | constructor(private content: Content, 90 | private renderer: Renderer, 91 | private zone: NgZone) {} 92 | 93 | 94 | ngOnInit() { 95 | this.content.fullscreen = true; 96 | 97 | //console.log("mode:", this.content._mode, "tabs:", this.content._tabs); 98 | console.log("ngOnInit() > navCtrl:", this.navCtrl); 99 | console.log("ngOnInit() > viewCtrl:", this.viewCtrl); 100 | 101 | 102 | this.viewCtrl.didEnter.subscribe(() => { 103 | //console.log("ScrollHide > viewCtrl.didEnter(), _tabsPlacement:", this.content._tabsPlacement); 104 | console.log("init()"); 105 | this.init(); 106 | this.content.resize(); 107 | }); 108 | 109 | this.viewCtrl.willLeave.subscribe(() => { 110 | console.log("resetStyle()"); 111 | this.resetStyle(); 112 | }); 113 | 114 | 115 | 116 | let win: any = window; 117 | win.temp = win.temp || {}; 118 | win.temp.content = this.content; 119 | win.temp.zone = this.zone; 120 | } 121 | 122 | ngOnDestroy() { 123 | console.log("ngOnDestroy()"); 124 | } 125 | 126 | private init() { 127 | this.y = 0; 128 | this.yPrev = 0; 129 | this.scrollTopPrev = this.content.scrollTop; 130 | 131 | 132 | 133 | let contentEle = this.content.getNativeElement(); 134 | let headerEle = contentEle.parentElement.querySelector("ion-header"); 135 | let footerEle = contentEle.parentElement.querySelector("ion-footer"); 136 | 137 | if(headerEle) { 138 | this.headerItems = $(headerEle) 139 | .children() 140 | .toArray().map(ele => new Item(ele)); 141 | } 142 | if(footerEle) { 143 | this.footerItems = $(footerEle) 144 | .children() 145 | .toArray().map(ele => new Item(ele)); 146 | } 147 | 148 | 149 | 150 | 151 | let hasTabbar = (this.content._tabs !== null); 152 | if(hasTabbar) { 153 | //this.tabbarEle = this.content._tabs._tabbar.nativeElement; 154 | let tabbarEle = ( this.content._tabs)._tabbar.nativeElement; 155 | 156 | if(this.content._tabsPlacement === "bottom") { 157 | this.footerItems.push(new Item(tabbarEle)); 158 | } else { 159 | this.headerItems.push(new Item(tabbarEle)); 160 | } 161 | } 162 | 163 | 164 | this.headerItems = this.headerItems.reverse(); 165 | this.footerItems = this.footerItems.reverse(); 166 | 167 | 168 | 169 | 170 | var contentTop = 0; 171 | 172 | console.group("headerItems") 173 | 174 | var shrinkableHeightSum = 0; 175 | this.headerItems.forEach((item, index) => { 176 | contentTop += item.ele.offsetHeight; 177 | 178 | // Navbar would not be raised without setting position to relative 179 | if(item.type === ItemType.Navbar) { 180 | $(item.ele).css("position", "relative") 181 | } 182 | 183 | 184 | // Prevent overlapping of bottom element 185 | if(item.type === ItemType.Tabbar) { 186 | $(item.ele).css("overflow", "hidden"); 187 | $(item.ele).children().css("align-self", "flex-end"); 188 | } else { 189 | $(item.ele).css("z-index", index + 1); 190 | } 191 | 192 | 193 | if(item.shrinkable) { 194 | item.setY( 195 | shrinkableHeightSum, 196 | shrinkableHeightSum + item.ele.offsetHeight 197 | ); 198 | shrinkableHeightSum += item.ele.offsetHeight; 199 | 200 | } else { 201 | item.setY( 202 | shrinkableHeightSum, 203 | shrinkableHeightSum 204 | ); 205 | } 206 | 207 | console.log(item, item.ele.offsetHeight); 208 | }); 209 | this.contentTop = contentTop; 210 | this.defaultEnd = this.contentTop * 2; 211 | this.endY = shrinkableHeightSum; 212 | 213 | console.groupEnd(); 214 | 215 | 216 | console.group("footerItems") 217 | this.footerItems.forEach(item => { 218 | console.log(item.ele, item.ele.offsetHeight); 219 | }); 220 | console.groupEnd(); 221 | 222 | console.log("contentTop:", contentTop, ", endY:", this.endY); 223 | 224 | 225 | /* 226 | ion-header { 227 | pointer-events: none; 228 | } 229 | 230 | ion-header > * { 231 | pointer-events: all; 232 | }*/ 233 | 234 | if(headerEle) { 235 | $(headerEle).css("pointer-events", "none"); 236 | $(headerEle).children().css("pointer-events", "all"); 237 | } 238 | if(footerEle) { 239 | $(footerEle).css("pointer-events", "none"); 240 | $(footerEle).children().css("pointer-events", "all"); 241 | } 242 | 243 | this.headerEle = headerEle; 244 | this.footerEle = footerEle; 245 | } 246 | 247 | 248 | private resetStyle() { 249 | this.headerItems.forEach((item, index) => { 250 | 251 | if(item.type === ItemType.Navbar) { 252 | $(item.ele).css("position", ""); 253 | } 254 | 255 | if(item.type === ItemType.Tabbar) { 256 | $(item.ele).css("overflow", ""); 257 | $(item.ele).children().css("align-self", "center"); 258 | } else { 259 | $(item.ele).css("z-index", ""); 260 | } 261 | 262 | }); 263 | 264 | if(this.headerEle) { 265 | $(this.headerEle).css("pointer-events", "none"); 266 | $(this.headerEle).children().css("pointer-events", "all"); 267 | } 268 | if(this.footerEle) { 269 | $(this.footerEle).css("pointer-events", "none"); 270 | $(this.footerEle).children().css("pointer-events", "all"); 271 | } 272 | 273 | 274 | // Header Items 275 | this.headerItems.forEach((item) => { 276 | 277 | 278 | $(item.ele).css("height", ""); 279 | $(item.ele).css("display", ""); 280 | $(item.ele).css("webkitTransform", ""); 281 | 282 | if(item.shrinkable) { 283 | $(item.ele).children().not(".toolbar-background") 284 | .each((index, navbarChild) => { 285 | $(navbarChild).css("opacity", ""); 286 | }); 287 | } 288 | }); 289 | 290 | 291 | // Footer Items 292 | this.footerItems.forEach((item) => { 293 | $(item.ele).css("webkitTransform", ""); 294 | }); 295 | } 296 | 297 | 298 | 299 | defaultDelay: number = 400 * 2; 300 | defaultDuration: number = 400; 301 | 302 | defaultEnd: number = 0; 303 | y: number = 0; 304 | yPrev: number = 0; 305 | scrollTopPrev: number = 0; 306 | 307 | @HostListener("ionScroll", ["$event"]) 308 | onContentScoll(event: ScrollEvent) { 309 | //console.log("ionScroll", event); //{ contentHeight: event.contentHeight, scrollHeight: event.scrollHeight }); 310 | //this.checkTabsPlacment(); 311 | 312 | /*if(!this.initiated) { 313 | this.initiated = true; 314 | this.init(); 315 | this.content.resize(); 316 | }*/ 317 | 318 | 319 | let maxScrollTop = event.scrollHeight - (event.contentTop + event.contentHeight + event.contentBottom); 320 | 321 | 322 | 323 | var duration = 0; 324 | let scrollTop = event.scrollTop; 325 | let scrollTopDiff = scrollTop - this.scrollTopPrev; 326 | 327 | let y = (scrollTop >= 0) ? constrain(this.yPrev + scrollTopDiff, 0, this.defaultEnd) : 0; 328 | 329 | 330 | 331 | //if we are at the bottom, animate the header/tabs back in 332 | //if (scrollView.getScrollMax().top - scrollTop <= contentTop) { 333 | // if (scrollTop >= maxScrollTop) { 334 | // y = 0; 335 | // duration = this.defaultDuration; 336 | // } 337 | 338 | this.scrollTopPrev = scrollTop; 339 | 340 | //if previous and current y are the same, no need to continue 341 | if (this.yPrev === y) { 342 | return; 343 | } 344 | this.yPrev = y; 345 | 346 | 347 | 348 | //console.log({ y: y, scrollTop: event.scrollTop, maxScrollTop: maxScrollTop}); 349 | 350 | 351 | this.modifyDom(y * 1.0, duration, event.domWrite); 352 | } 353 | 354 | 355 | 356 | private modifyDom(y: number, duration: number, dowWrite: DomWrite) { 357 | dowWrite(() => { 358 | 359 | // Header Items 360 | this.headerItems.forEach((item) => { 361 | 362 | let dy = constrain((y - item.startY), 0, (this.endY - item.startY)); 363 | 364 | if(item.type === ItemType.Tabbar) { 365 | var val = constrain((dy / this.content._tabbarHeight), 0, 1); 366 | 367 | console.log("val:", val); 368 | 369 | let height = this.content._tabbarHeight * (1 - val); 370 | if(height >= 0) { 371 | $(item.ele).css("height", height + "px"); 372 | $(item.ele).css("display", ""); 373 | } else { 374 | $(item.ele).css("display", "none"); 375 | } 376 | 377 | } else { 378 | var val = constrain((dy / item.ele.offsetHeight), 0, 1); 379 | 380 | this.translateElementY(item.ele, -dy, duration); 381 | } 382 | if(item.shrinkable) { 383 | //let fadeAmt = constrain(1 - (dy / item.ele.offsetHeight), 0, 1); 384 | let fadeAmt = 1 - val; 385 | 386 | $(item.ele).children().not(".toolbar-background") 387 | .each((index, navbarChild) => { 388 | $(navbarChild).css("opacity", fadeAmt); 389 | }); 390 | } 391 | }); 392 | 393 | 394 | 395 | // Footer Items 396 | this.footerItems.forEach((item) => { 397 | this.translateElementY(item.ele, y, duration); 398 | }); 399 | }); 400 | } 401 | 402 | private translateElementY(ele: HTMLElement, y: number, duration: number) { 403 | this.renderer.setElementStyle(ele, "webkitTransform", `translate3d(0, ${y}px,0)`); 404 | 405 | /*if (duration && !ele.style.transitionDuration) { 406 | ele.style.transitionDuration = duration + "ms"; 407 | setTimeout(() => { 408 | ele.style.transitionDuration = ""; 409 | }, this.defaultDelay); 410 | }*/ 411 | } 412 | } 413 | -------------------------------------------------------------------------------- /src/components/scroll-hide/dev/scroll-hide-v2.0.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Directive, 3 | ElementRef, NgZone, HostListener, Input, Renderer, 4 | OnInit, OnDestroy, 5 | } from '@angular/core'; 6 | 7 | import { Content, ScrollEvent, NavController, ViewController } from 'ionic-angular' 8 | 9 | 10 | import * as $ from 'jquery' 11 | 12 | 13 | /* 14 | 15 | 16 | ion-header { 17 | pointer-events: none; 18 | } 19 | ion-header > * { 20 | pointer-events: all; 21 | } 22 | 23 | 24 | 25 | 26 | ion-header > ion-navbar { 27 | position: relative; 28 | } 29 | 30 | 31 | 32 | ion-tabs > .tabbar { 33 | overflow: hidden; 34 | } 35 | ion-tabs > .tabbar > *{ 36 | align-self: flex-end; 37 | } 38 | 39 | */ 40 | 41 | 42 | type DomWrite = (fn: (timeStamp?: number) => void, ctx?: any) => void; 43 | 44 | 45 | function constrain(val: number, min: number, max: number) { 46 | return Math.min(max, Math.max(val, min)); 47 | } 48 | 49 | function forEach(children: HTMLCollection, callback: (ele: HTMLElement) => void) { 50 | for(var i = 0; i < children.length; i++) { 51 | callback( children[i]); 52 | } 53 | } 54 | 55 | 56 | /* 57 | interface Item { 58 | ele: HTMLElement; 59 | startY: number; 60 | endY: number; 61 | unshrinkable: boolean; 62 | } 63 | */ 64 | 65 | 66 | enum ItemType { 67 | Navbar, Toolbar, Tabbar 68 | } 69 | class Item { 70 | public type: ItemType; 71 | public shrinkable: boolean; 72 | public startY = 0; 73 | public endY = Infinity; 74 | public height: number; 75 | 76 | constructor(public ele: HTMLElement) { 77 | 78 | if(ele.classList.contains("tabbar")) { 79 | this.type = ItemType.Tabbar; 80 | } else if(ele.tagName === "ION-NAVBAR") { 81 | this.type = ItemType.Navbar; 82 | } else { 83 | this.type = ItemType.Toolbar; 84 | } 85 | 86 | this.shrinkable = !ele.classList.contains("unshrinkable"); 87 | this.height = ele.offsetHeight; 88 | } 89 | 90 | setY(startY: number, endY: number) { 91 | this.startY = startY; 92 | this.endY = endY; 93 | } 94 | 95 | setStartY(startY: number) { 96 | this.startY = startY; 97 | } 98 | setEndY(endY: number) { 99 | this.endY = endY; 100 | } 101 | 102 | } 103 | 104 | 105 | @Directive({ 106 | selector: '[scroll-hide]' // Attribute selector 107 | }) 108 | export class ScrollHide implements OnInit, OnDestroy { 109 | 110 | @Input() navCtrl: NavController; 111 | @Input() viewCtrl: ViewController; 112 | 113 | 114 | headerItems: Item[] = []; 115 | footerItems: Item[] = []; 116 | 117 | headerEle: HTMLElement; 118 | footerEle: HTMLElement; 119 | 120 | //--------------------------------- 121 | 122 | defaultDelay: number = 400 * 2; 123 | defaultDuration: number = 400; 124 | 125 | 126 | headerHeight: number; 127 | footerHeight: number; 128 | endY: number; 129 | 130 | defaultEnd: number = 0; 131 | y: number = 0; 132 | yPrev: number = 0; 133 | scrollTopPrev: number = 0; 134 | 135 | //--------------------------------- 136 | 137 | 138 | constructor(private content: Content, 139 | private renderer: Renderer, 140 | private zone: NgZone) {} 141 | 142 | 143 | ngOnInit() { 144 | this.content.fullscreen = true; 145 | 146 | 147 | //console.log("ngOnInit() > navCtrl:", this.navCtrl); 148 | //console.log("ngOnInit() > viewCtrl:", this.viewCtrl); 149 | 150 | 151 | this.viewCtrl.didEnter.subscribe(() => { 152 | //console.log("ScrollHide > viewCtrl.didEnter(), _tabsPlacement:", this.content._tabsPlacement); 153 | console.log("init()"); 154 | this.init(); 155 | }); 156 | this.viewCtrl.willLeave.subscribe(() => { 157 | console.log("resetStyle()"); 158 | this.resetStyle(); 159 | }); 160 | 161 | 162 | this.content.ionScroll.subscribe(event => { 163 | this.onContentScroll(event, false); 164 | }); 165 | this.content.ionScrollEnd.subscribe(event => { 166 | console.log("ionScrollEnd!"); 167 | this.onContentScroll(event, true); 168 | }); 169 | 170 | 171 | 172 | let win: any = window; 173 | win.temp = win.temp || {}; 174 | win.temp.content = this.content; 175 | win.temp.zone = this.zone; 176 | } 177 | 178 | ngOnDestroy() { 179 | console.log("ngOnDestroy()"); 180 | } 181 | 182 | private init() { 183 | this.y = 0; 184 | this.yPrev = 0; 185 | this.scrollTopPrev = this.content.scrollTop; 186 | 187 | 188 | 189 | this.content.resize(); 190 | 191 | 192 | let contentEle = this.content.getNativeElement(); 193 | let headerEle = contentEle.parentElement.querySelector("ion-header"); 194 | let footerEle = contentEle.parentElement.querySelector("ion-footer"); 195 | 196 | if(headerEle) { 197 | this.headerItems = $(headerEle) 198 | .children() 199 | .toArray().map(ele => new Item(ele)); 200 | } else { 201 | this.headerItems = []; 202 | } 203 | 204 | if(footerEle) { 205 | this.footerItems = $(footerEle) 206 | .children() 207 | .toArray().map(ele => new Item(ele)); 208 | } else { 209 | this.footerItems = []; 210 | } 211 | 212 | 213 | 214 | 215 | let hasTabbar = (this.content._tabs !== null); 216 | if(hasTabbar) { 217 | //this.tabbarEle = this.content._tabs._tabbar.nativeElement; 218 | let tabbarEle = ( this.content._tabs)._tabbar.nativeElement; 219 | 220 | if(this.content._tabsPlacement === "bottom") { 221 | this.footerItems.push(new Item(tabbarEle)); 222 | } else { 223 | this.headerItems.push(new Item(tabbarEle)); 224 | } 225 | } 226 | 227 | 228 | this.headerItems = this.headerItems.reverse(); 229 | this.footerItems = this.footerItems.reverse(); 230 | 231 | 232 | 233 | 234 | var headerHeight = 0, 235 | footerHeight = 0; 236 | 237 | console.group("headerItems") 238 | 239 | var shrinkableHeightSum = 0; 240 | this.headerItems.forEach((item, index) => { 241 | headerHeight += item.ele.offsetHeight; 242 | 243 | // Navbar would not be raised without setting position to relative 244 | if(item.type === ItemType.Navbar) { 245 | $(item.ele).css("position", "relative") 246 | } 247 | 248 | 249 | // Prevent overlapping of bottom element 250 | if(item.type === ItemType.Tabbar) { 251 | $(item.ele).css("overflow", "hidden"); 252 | $(item.ele).children().css("align-self", "flex-end"); 253 | } else { 254 | $(item.ele).css("z-index", index + 1); 255 | } 256 | 257 | 258 | if(item.shrinkable) { 259 | item.setStartY(shrinkableHeightSum); 260 | item.setEndY(Infinity); //item.setEndY(shrinkableHeightSum + item.ele.offsetHeight); 261 | shrinkableHeightSum += item.ele.offsetHeight; 262 | } else { 263 | item.setStartY(shrinkableHeightSum); 264 | item.setEndY(Infinity); //item.setEndY(shrinkableHeightSum); 265 | } 266 | 267 | console.log(item, item.ele.offsetHeight); 268 | }); 269 | this.headerHeight = headerHeight; 270 | this.defaultEnd = this.headerHeight * 2; 271 | this.endY = shrinkableHeightSum; 272 | 273 | console.groupEnd(); 274 | 275 | 276 | console.group("footerItems") 277 | this.footerItems.forEach(item => { 278 | footerHeight += item.ele.offsetHeight; 279 | console.log(item.ele, item.ele.offsetHeight); 280 | }); 281 | this.footerHeight = footerHeight; 282 | console.groupEnd(); 283 | 284 | console.log("headerHeight:", headerHeight, ", footerHeight:", footerHeight, ", endY:", this.endY); 285 | 286 | 287 | /* 288 | ion-header { 289 | pointer-events: none; 290 | } 291 | 292 | ion-header > * { 293 | pointer-events: all; 294 | }*/ 295 | 296 | if(headerEle) { 297 | $(headerEle).css("pointer-events", "none"); 298 | $(headerEle).children().css("pointer-events", "all"); 299 | } 300 | if(footerEle) { 301 | $(footerEle).css("pointer-events", "none"); 302 | $(footerEle).children().css("pointer-events", "all"); 303 | } 304 | 305 | this.headerEle = headerEle; 306 | this.footerEle = footerEle; 307 | } 308 | 309 | 310 | private resetStyle() { 311 | this.headerItems.forEach((item, index) => { 312 | 313 | if(item.type === ItemType.Navbar) { 314 | $(item.ele).css("position", ""); 315 | } 316 | 317 | if(item.type === ItemType.Tabbar) { 318 | $(item.ele).css("overflow", ""); 319 | $(item.ele).children().css("align-self", "center"); 320 | } else { 321 | $(item.ele).css("z-index", ""); 322 | } 323 | 324 | }); 325 | 326 | if(this.headerEle) { 327 | $(this.headerEle).css("pointer-events", "none"); 328 | $(this.headerEle).children().css("pointer-events", "all"); 329 | } 330 | if(this.footerEle) { 331 | $(this.footerEle).css("pointer-events", "none"); 332 | $(this.footerEle).children().css("pointer-events", "all"); 333 | } 334 | 335 | 336 | // Header Items 337 | this.headerItems.forEach((item) => { 338 | 339 | $(item.ele).css("height", ""); 340 | $(item.ele).css("display", ""); 341 | $(item.ele).css("webkitTransform", ""); 342 | 343 | if(item.shrinkable) { 344 | $(item.ele).children().not(".toolbar-background") 345 | .each((index, navbarChild) => { 346 | $(navbarChild).css("opacity", ""); 347 | }); 348 | } 349 | }); 350 | 351 | 352 | // Footer Items 353 | this.footerItems.forEach((item) => { 354 | $(item.ele).css("webkitTransform", ""); 355 | }); 356 | } 357 | 358 | 359 | 360 | 361 | 362 | 363 | hasScrollToBottomBefore = false; 364 | 365 | 366 | //@HostListener("ionScroll", ["$event", "false"]) 367 | onContentScroll(event: ScrollEvent, isMoveEnd: boolean) { 368 | //console.log("ionScroll", event); //{ contentHeight: event.contentHeight, scrollHeight: event.scrollHeight }); 369 | //this.checkTabsPlacment(); 370 | 371 | /*if(!this.initiated) { 372 | this.initiated = true; 373 | this.init(); 374 | this.content.resize(); 375 | }*/ 376 | 377 | 378 | let maxScrollTop = event.scrollHeight - (event.contentTop + event.contentHeight + event.contentBottom); 379 | 380 | 381 | 382 | var duration = 0; 383 | let scrollTopDiff = event.scrollTop - this.scrollTopPrev; 384 | 385 | let y = (event.scrollTop >= 0) ? constrain(this.yPrev + scrollTopDiff, 0, this.defaultEnd) : 0; 386 | 387 | 388 | 389 | //if we are at the bottom, animate the header/tabs back in 390 | if (event.scrollTop >= maxScrollTop - this.footerHeight) { 391 | 392 | if(this.hasScrollToBottomBefore) { 393 | y = 0; 394 | duration = this.defaultDuration; 395 | } 396 | 397 | if(isMoveEnd) { 398 | this.hasScrollToBottomBefore = true; 399 | this.content.setScrollElementStyle("padding-bottom", (this.content._pBottom + this.footerHeight) + "px"); 400 | } 401 | 402 | } else { 403 | this.hasScrollToBottomBefore = false; 404 | this.content.setScrollElementStyle("padding-bottom", (this.content._pBottom) + "px"); 405 | } 406 | 407 | 408 | console.log({ 409 | y: y, 410 | endY: this.endY, 411 | 412 | scrollTop: event.scrollTop, 413 | maxScrollTop: maxScrollTop, 414 | footerHeight: this.footerHeight, 415 | }, isMoveEnd); 416 | 417 | 418 | 419 | 420 | 421 | //if previous and current y are the same, no need to continue 422 | if (this.yPrev !== y) { 423 | //this.modifyDom(y, duration, event.domWrite); 424 | 425 | let yDiff = y - this.yPrev; 426 | if(-30 < yDiff && yDiff < 30) { 427 | this.modifyDom(y, duration, event.domWrite); 428 | } else { 429 | this.modifyDom(y, 200, event.domWrite); 430 | } 431 | } 432 | 433 | //console.log({ y: y, scrollTop: event.scrollTop, maxScrollTop: maxScrollTop}); 434 | 435 | 436 | this.yPrev = y; 437 | this.scrollTopPrev = event.scrollTop; 438 | } 439 | 440 | 441 | 442 | private modifyDom(y: number, duration: number, dowWrite: DomWrite) { 443 | dowWrite(() => { 444 | 445 | // Header Items 446 | this.headerItems.forEach((item) => { 447 | 448 | let dy = constrain((y - item.startY), 0, (this.endY - item.startY)); 449 | 450 | if(item.type === ItemType.Tabbar) { 451 | var val = constrain((dy / this.content._tabbarHeight), 0, 1); 452 | 453 | console.log("val:", val); 454 | 455 | let height = this.content._tabbarHeight * (1 - val); 456 | if(height >= 0) { 457 | $(item.ele).css("height", height + "px"); 458 | $(item.ele).css("display", ""); 459 | } else { 460 | $(item.ele).css("display", "none"); 461 | } 462 | 463 | } else { 464 | var val = constrain((dy / item.ele.offsetHeight), 0, 1); 465 | //var val = constrain((dy / item.height), 0, 1); 466 | 467 | this.translateElementY(item.ele, -dy, duration); 468 | /*if(dy <= item.endY) { 469 | $(item.ele).css({ 470 | height: item.height - dy, 471 | minHeight: item.height - dy, 472 | }) 473 | } else { 474 | this.translateElementY(item.ele, -dy, duration); 475 | }*/ 476 | } 477 | if(item.shrinkable) { 478 | //let fadeAmt = constrain(1 - (dy / item.ele.offsetHeight), 0, 1); 479 | let fadeAmt = 1 - val; 480 | 481 | $(item.ele).children().not(".toolbar-background") 482 | .each((index, navbarChild) => { 483 | $(navbarChild).css("opacity", fadeAmt); 484 | }); 485 | } 486 | }); 487 | 488 | 489 | 490 | // Footer Items 491 | this.footerItems.forEach((item) => { 492 | this.translateElementY(item.ele, y, duration); 493 | }); 494 | 495 | 496 | // Content 497 | /*if(y <= this.endY) { 498 | this.content.setScrollElementStyle("margin-top", (this.headerHeight - y) + "px"); 499 | } else { 500 | this.content.setScrollElementStyle("margin-top", (this.headerHeight - this.endY) + "px"); 501 | } 502 | 503 | if(y <= this.footerHeight) { 504 | this.content.setScrollElementStyle("margin-bottom", (this.footerHeight - y) + "px"); 505 | }*/ 506 | }); 507 | } 508 | 509 | private translateElementY(ele: HTMLElement, y: number, duration: number) { 510 | this.renderer.setElementStyle(ele, "webkitTransform", `translate3d(0, ${y}px,0)`); 511 | 512 | if (duration && !ele.style.transitionDuration) { 513 | ele.style.transitionDuration = duration + "ms"; 514 | setTimeout(() => { 515 | ele.style.transitionDuration = ""; 516 | }, this.defaultDelay); 517 | } 518 | } 519 | } 520 | -------------------------------------------------------------------------------- /src/components/scroll-hide/dev/scroll-hide-v2.1.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Directive, 3 | ElementRef, NgZone, HostListener, Input, Renderer, 4 | OnInit, OnDestroy, 5 | } from '@angular/core'; 6 | 7 | import { Content, ScrollEvent, NavController, ViewController } from 'ionic-angular' 8 | 9 | 10 | import * as $ from 'jquery' 11 | 12 | 13 | /* 14 | 15 | 16 | ion-header { 17 | pointer-events: none; 18 | } 19 | ion-header > * { 20 | pointer-events: all; 21 | } 22 | 23 | 24 | 25 | 26 | ion-header > ion-navbar { 27 | position: relative; 28 | } 29 | 30 | 31 | 32 | ion-tabs > .tabbar { 33 | overflow: hidden; 34 | } 35 | ion-tabs > .tabbar > *{ 36 | align-self: flex-end; 37 | } 38 | 39 | */ 40 | 41 | 42 | type DomWrite = (fn: (timeStamp?: number) => void, ctx?: any) => void; 43 | 44 | 45 | function constrain(val: number, min: number, max: number) { 46 | return Math.min(max, Math.max(val, min)); 47 | } 48 | 49 | function forEach(children: HTMLCollection, callback: (ele: HTMLElement) => void) { 50 | for(var i = 0; i < children.length; i++) { 51 | callback( children[i]); 52 | } 53 | } 54 | 55 | 56 | /* 57 | interface Item { 58 | ele: HTMLElement; 59 | startY: number; 60 | endY: number; 61 | unshrinkable: boolean; 62 | } 63 | */ 64 | 65 | 66 | enum ItemType { 67 | Navbar, Toolbar, Tabbar 68 | } 69 | class Item { 70 | public type: ItemType; 71 | public height: number; 72 | public shrinkable: boolean; 73 | public scrollShrinkVal: number; 74 | 75 | public startY = 0; 76 | public endY = 0; 77 | 78 | constructor(public ele: HTMLElement) { 79 | 80 | if(ele.classList.contains("tabbar")) { 81 | this.type = ItemType.Tabbar; 82 | } else if(ele.tagName === "ION-NAVBAR") { 83 | this.type = ItemType.Navbar; 84 | } else { 85 | this.type = ItemType.Toolbar; 86 | } 87 | 88 | this.height = ele.offsetHeight; 89 | this.shrinkable = !ele.classList.contains("unshrinkable"); 90 | 91 | var scrollShrinkVal = parseFloat($(ele).attr("scroll-hide-val")); 92 | if(this.shrinkable) { 93 | if(isNaN(scrollShrinkVal)) scrollShrinkVal = 1; 94 | } else { 95 | scrollShrinkVal = 0; 96 | } 97 | this.scrollShrinkVal = scrollShrinkVal; 98 | } 99 | 100 | setY(startY: number, endY: number) { 101 | this.startY = startY; 102 | this.endY = endY; 103 | } 104 | 105 | setStartY(startY: number) { 106 | this.startY = startY; 107 | } 108 | setEndY(endY: number) { 109 | this.endY = endY; 110 | } 111 | 112 | } 113 | 114 | 115 | @Directive({ 116 | selector: '[scroll-hide]' // Attribute selector 117 | }) 118 | export class ScrollHide implements OnInit, OnDestroy { 119 | 120 | @Input() navCtrl: NavController; 121 | @Input() viewCtrl: ViewController; 122 | 123 | 124 | headerItems: Item[] = []; 125 | footerItems: Item[] = []; 126 | 127 | headerEle: HTMLElement; 128 | footerEle: HTMLElement; 129 | 130 | //--------------------------------- 131 | 132 | defaultDelay: number = 400 * 2; 133 | defaultDuration: number = 400; 134 | 135 | 136 | headerHeight: number; 137 | footerHeight: number; 138 | endY: number; 139 | 140 | defaultEnd: number = 0; 141 | y: number = 0; 142 | yPrev: number = 0; 143 | scrollTopPrev: number = 0; 144 | 145 | //--------------------------------- 146 | 147 | 148 | constructor(private content: Content, 149 | private renderer: Renderer, 150 | private zone: NgZone) {} 151 | 152 | 153 | ngOnInit() { 154 | this.content.fullscreen = true; 155 | 156 | 157 | //console.log("ngOnInit() > navCtrl:", this.navCtrl); 158 | //console.log("ngOnInit() > viewCtrl:", this.viewCtrl); 159 | 160 | 161 | this.viewCtrl.didEnter.subscribe(() => { 162 | //console.log("ScrollHide > viewCtrl.didEnter(), _tabsPlacement:", this.content._tabsPlacement); 163 | console.log("init()"); 164 | this.init(); 165 | }); 166 | this.viewCtrl.willLeave.subscribe(() => { 167 | console.log("resetStyle()"); 168 | this.resetStyle(); 169 | }); 170 | 171 | 172 | this.content.ionScroll.subscribe(event => { 173 | this.onContentScroll(event, false); 174 | }); 175 | this.content.ionScrollEnd.subscribe(event => { 176 | console.log("ionScrollEnd!"); 177 | this.onContentScroll(event, true); 178 | }); 179 | 180 | 181 | 182 | let win: any = window; 183 | win.temp = win.temp || {}; 184 | win.temp.content = this.content; 185 | win.temp.zone = this.zone; 186 | } 187 | 188 | ngOnDestroy() { 189 | console.log("ngOnDestroy()"); 190 | } 191 | 192 | private init() { 193 | this.y = 0; 194 | this.yPrev = 0; 195 | this.scrollTopPrev = this.content.scrollTop; 196 | 197 | 198 | 199 | this.content.resize(); 200 | 201 | 202 | let contentEle = this.content.getNativeElement(); 203 | let headerEle = contentEle.parentElement.querySelector("ion-header"); 204 | let footerEle = contentEle.parentElement.querySelector("ion-footer"); 205 | 206 | if(headerEle) { 207 | this.headerItems = $(headerEle) 208 | .children() 209 | .toArray().map(ele => new Item(ele)); 210 | } else { 211 | this.headerItems = []; 212 | } 213 | 214 | if(footerEle) { 215 | this.footerItems = $(footerEle) 216 | .children() 217 | .toArray().map(ele => new Item(ele)); 218 | } else { 219 | this.footerItems = []; 220 | } 221 | 222 | 223 | 224 | 225 | let hasTabbar = (this.content._tabs !== null); 226 | if(hasTabbar) { 227 | //this.tabbarEle = this.content._tabs._tabbar.nativeElement; 228 | let tabbarEle = ( this.content._tabs)._tabbar.nativeElement; 229 | 230 | if(this.content._tabsPlacement === "bottom") { 231 | this.footerItems.push(new Item(tabbarEle)); 232 | } else { 233 | this.headerItems.push(new Item(tabbarEle)); 234 | } 235 | } 236 | 237 | 238 | this.headerItems = this.headerItems.reverse(); 239 | this.footerItems = this.footerItems.reverse(); 240 | 241 | 242 | 243 | 244 | var headerHeight = 0, 245 | footerHeight = 0; 246 | 247 | console.group("headerItems") 248 | 249 | var shrinkableHeightSum = 0; 250 | this.headerItems.forEach((item, index) => { 251 | headerHeight += item.ele.offsetHeight; 252 | 253 | // Navbar would not be raised without setting position to relative 254 | if(item.type === ItemType.Navbar) { 255 | $(item.ele).css("position", "relative") 256 | } 257 | 258 | 259 | // Prevent overlapping of bottom element 260 | if(item.type === ItemType.Tabbar) { 261 | $(item.ele).css("overflow", "hidden"); 262 | $(item.ele).children().css("align-self", "flex-end"); 263 | } else { 264 | $(item.ele).css("z-index", index + 1); 265 | } 266 | 267 | 268 | // For shrinking 269 | $(item.ele).css("padding-top", "0"); 270 | $(item.ele).css("padding-bottom", "0"); 271 | 272 | 273 | 274 | if(item.shrinkable) { 275 | item.setStartY(shrinkableHeightSum); 276 | //item.setEndY(shrinkableHeightSum + (item.height * (1 - item.scrollShrinkVal))); 277 | //shrinkableHeightSum += item.height * (1 - item.scrollShrinkVal); 278 | item.setEndY(shrinkableHeightSum + (item.height * (item.scrollShrinkVal))); 279 | shrinkableHeightSum += item.height * item.scrollShrinkVal; 280 | //item.setEndY(shrinkableHeightSum + shrinkHeight); 281 | //shrinkableHeightSum += shrinkHeight; 282 | } else { 283 | item.setStartY(shrinkableHeightSum); 284 | item.setEndY(shrinkableHeightSum); 285 | //shrinkableHeightSum += item.height; 286 | } 287 | 288 | console.log(item, item.ele.offsetHeight); 289 | }); 290 | this.headerHeight = headerHeight; 291 | this.defaultEnd = this.headerHeight * 2; 292 | this.endY = shrinkableHeightSum; 293 | 294 | console.groupEnd(); 295 | 296 | 297 | console.group("footerItems") 298 | this.footerItems.forEach(item => { 299 | footerHeight += item.ele.offsetHeight; 300 | console.log(item.ele, item.ele.offsetHeight); 301 | }); 302 | this.footerHeight = footerHeight; 303 | console.groupEnd(); 304 | 305 | console.log("headerHeight:", headerHeight, ", footerHeight:", footerHeight, ", endY:", this.endY); 306 | 307 | 308 | /* 309 | ion-header { 310 | pointer-events: none; 311 | } 312 | 313 | ion-header > * { 314 | pointer-events: all; 315 | }*/ 316 | 317 | if(headerEle) { 318 | $(headerEle).css("pointer-events", "none"); 319 | $(headerEle).children().css("pointer-events", "all"); 320 | } 321 | if(footerEle) { 322 | $(footerEle).css("pointer-events", "none"); 323 | $(footerEle).children().css("pointer-events", "all"); 324 | } 325 | 326 | this.headerEle = headerEle; 327 | this.footerEle = footerEle; 328 | } 329 | 330 | 331 | private resetStyle() { 332 | this.headerItems.forEach((item, index) => { 333 | 334 | if(item.type === ItemType.Navbar) { 335 | $(item.ele).css("position", ""); 336 | } 337 | 338 | if(item.type === ItemType.Tabbar) { 339 | $(item.ele).css("overflow", ""); 340 | $(item.ele).children().css("align-self", "center"); 341 | } else { 342 | $(item.ele).css("z-index", ""); 343 | } 344 | 345 | }); 346 | 347 | if(this.headerEle) { 348 | $(this.headerEle).css("pointer-events", "none"); 349 | $(this.headerEle).children().css("pointer-events", "all"); 350 | } 351 | if(this.footerEle) { 352 | $(this.footerEle).css("pointer-events", "none"); 353 | $(this.footerEle).children().css("pointer-events", "all"); 354 | } 355 | 356 | 357 | // Header Items 358 | this.headerItems.forEach((item) => { 359 | 360 | $(item.ele).css("height", ""); 361 | $(item.ele).css("display", ""); 362 | $(item.ele).css("webkitTransform", ""); 363 | 364 | if(item.shrinkable) { 365 | $(item.ele).children().not(".toolbar-background") 366 | .each((index, navbarChild) => { 367 | $(navbarChild).css("opacity", ""); 368 | }); 369 | } 370 | }); 371 | 372 | 373 | // Footer Items 374 | this.footerItems.forEach((item) => { 375 | $(item.ele).css("webkitTransform", ""); 376 | }); 377 | } 378 | 379 | 380 | 381 | 382 | 383 | 384 | hasScrollToBottomBefore = false; 385 | 386 | 387 | //@HostListener("ionScroll", ["$event", "false"]) 388 | onContentScroll(event: ScrollEvent, isMoveEnd: boolean) { 389 | //console.log("ionScroll", event); //{ contentHeight: event.contentHeight, scrollHeight: event.scrollHeight }); 390 | //this.checkTabsPlacment(); 391 | 392 | /*if(!this.initiated) { 393 | this.initiated = true; 394 | this.init(); 395 | this.content.resize(); 396 | }*/ 397 | 398 | 399 | let maxScrollTop = event.scrollHeight - (event.contentTop + event.contentHeight + event.contentBottom); 400 | 401 | 402 | 403 | var duration = 0; 404 | let scrollTopDiff = event.scrollTop - this.scrollTopPrev; 405 | 406 | let y = (event.scrollTop >= 0) ? constrain(this.yPrev + scrollTopDiff, 0, this.defaultEnd) : 0; 407 | 408 | 409 | 410 | //if we are at the bottom, animate the header/tabs back in 411 | if (event.scrollTop >= maxScrollTop - this.footerHeight) { 412 | 413 | if(this.hasScrollToBottomBefore) { 414 | y = 0; 415 | duration = this.defaultDuration; 416 | } 417 | 418 | if(isMoveEnd) { 419 | this.hasScrollToBottomBefore = true; 420 | this.content.setScrollElementStyle("padding-bottom", (this.content._pBottom + this.footerHeight) + "px"); 421 | } 422 | 423 | } else { 424 | this.hasScrollToBottomBefore = false; 425 | this.content.setScrollElementStyle("padding-bottom", (this.content._pBottom) + "px"); 426 | } 427 | 428 | 429 | /*console.log({ 430 | y: y, 431 | endY: this.endY, 432 | 433 | scrollTop: event.scrollTop, 434 | maxScrollTop: maxScrollTop, 435 | footerHeight: this.footerHeight, 436 | }, isMoveEnd);*/ 437 | 438 | 439 | 440 | 441 | 442 | //if previous and current y are the same, no need to continue 443 | if (this.yPrev !== y) { 444 | //this.modifyDom(y, duration, event.domWrite); 445 | 446 | let yDiff = y - this.yPrev; 447 | if(-30 < yDiff && yDiff < 30) { 448 | this.modifyDom(y, duration, event.domWrite); 449 | } else { 450 | this.modifyDom(y, 200, event.domWrite); 451 | } 452 | } 453 | 454 | //console.log({ y: y, scrollTop: event.scrollTop, maxScrollTop: maxScrollTop}); 455 | 456 | 457 | this.yPrev = y; 458 | this.scrollTopPrev = event.scrollTop; 459 | } 460 | 461 | 462 | 463 | private modifyDom(y: number, duration: number, dowWrite: DomWrite) { 464 | dowWrite(() => { 465 | y = constrain(y, 0, this.endY); 466 | 467 | 468 | console.group(); 469 | 470 | 471 | // Header Items 472 | this.headerItems.forEach((item) => { 473 | 474 | //let dy = constrain((y - item.startY), 0, Infinity); 475 | //let val = constrain((dy / item.height), 0, 1); 476 | 477 | if(item.type === ItemType.Tabbar) { 478 | let dy = constrain((y - item.startY), 0, Infinity); 479 | let val = constrain((dy / item.height), 0, 1); 480 | 481 | //var val = constrain((dy / this.content._tabbarHeight), 0, 1); 482 | 483 | console.log("val:", val); 484 | 485 | let height = this.content._tabbarHeight * (1 - val); 486 | if(height >= 0) { 487 | $(item.ele).css("height", height + "px"); 488 | $(item.ele).css("display", ""); 489 | } else { 490 | $(item.ele).css("display", "none"); 491 | } 492 | 493 | } else { 494 | let dy_shrink = constrain((y - item.startY), 0, item.endY - item.startY); 495 | let dy_translate = constrain((y - item.endY), 0, Infinity); 496 | 497 | //var val = constrain((dy / item.height), 0, 1); 498 | 499 | //this.translateElementY(item.ele, -dy, duration); 500 | //if(item.scrollShrinkVal === 0.3) 501 | console.log(y, "shrink:", dy_shrink, "tranalate:", dy_translate); 502 | 503 | if(y <= item.endY) { 504 | let val = constrain((dy_shrink / item.height), 0, 1); 505 | $(item.ele).css({ 506 | height: item.height - dy_shrink, 507 | minHeight: item.height - dy_shrink, 508 | marginBottom: dy_shrink, 509 | }) 510 | this.translateElementY(item.ele, 0, duration); 511 | } else { 512 | //this.translateElementY(item.ele, -dy, duration); 513 | let val = constrain((dy_translate / item.height), 0, 1); 514 | 515 | $(item.ele).css({ 516 | height: item.height - (item.endY - item.startY), 517 | minHeight: item.height - (item.endY - item.startY), 518 | marginBottom: (item.endY - item.startY), 519 | }) 520 | this.translateElementY(item.ele, -dy_translate, duration); 521 | } 522 | } 523 | /*if(item.shrinkable) { 524 | //let fadeAmt = constrain(1 - (dy / item.ele.offsetHeight), 0, 1); 525 | let fadeAmt = 1 - val; 526 | 527 | $(item.ele).children().not(".toolbar-background") 528 | .each((index, navbarChild) => { 529 | $(navbarChild).css("opacity", fadeAmt); 530 | }); 531 | }*/ 532 | }); 533 | console.groupEnd(); 534 | 535 | 536 | // Footer Items 537 | this.footerItems.forEach((item) => { 538 | this.translateElementY(item.ele, y, duration); 539 | }); 540 | 541 | 542 | // Content 543 | /*if(y <= this.endY) { 544 | this.content.setScrollElementStyle("margin-top", (this.headerHeight - y) + "px"); 545 | } else { 546 | this.content.setScrollElementStyle("margin-top", (this.headerHeight - this.endY) + "px"); 547 | } 548 | 549 | if(y <= this.footerHeight) { 550 | this.content.setScrollElementStyle("margin-bottom", (this.footerHeight - y) + "px"); 551 | }*/ 552 | }); 553 | } 554 | 555 | private translateElementY(ele: HTMLElement, y: number, duration: number) { 556 | this.renderer.setElementStyle(ele, "webkitTransform", `translate3d(0, ${y}px,0)`); 557 | 558 | if (duration && !ele.style.transitionDuration) { 559 | ele.style.transitionDuration = duration + "ms"; 560 | setTimeout(() => { 561 | ele.style.transitionDuration = ""; 562 | }, this.defaultDelay); 563 | } 564 | } 565 | } 566 | -------------------------------------------------------------------------------- /src/components/scroll-hide/dev/scroll-hide-v2.2.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Directive, 3 | ElementRef, NgZone, HostListener, Input, Renderer, 4 | OnInit, OnDestroy, 5 | } from '@angular/core'; 6 | 7 | import { Content, ScrollEvent, NavController, ViewController } from 'ionic-angular' 8 | 9 | 10 | import * as $ from 'jquery' 11 | 12 | 13 | /* 14 | 15 | 16 | ion-header { 17 | pointer-events: none; 18 | } 19 | ion-header > * { 20 | pointer-events: all; 21 | } 22 | 23 | 24 | 25 | 26 | ion-header > ion-navbar { 27 | position: relative; 28 | } 29 | 30 | 31 | 32 | ion-tabs > .tabbar { 33 | overflow: hidden; 34 | } 35 | ion-tabs > .tabbar > *{ 36 | align-self: flex-end; 37 | } 38 | 39 | */ 40 | 41 | 42 | type DomWrite = (fn: (timeStamp?: number) => void, ctx?: any) => void; 43 | 44 | 45 | function constrain(val: number, min: number, max: number) { 46 | return Math.min(max, Math.max(val, min)); 47 | } 48 | 49 | 50 | 51 | 52 | 53 | enum ItemType { 54 | Navbar, Toolbar, Tabbar 55 | } 56 | enum ItemTransition { 57 | Static, Translate, Shrink 58 | } 59 | 60 | /* 61 | * startY EndY 62 | * shrinkStart TransitionStart MoveStart 63 | * | ------------- | ---------------- | ----------> 64 | * Shrink Translate 65 | */ 66 | class Item { 67 | public type: ItemType; 68 | public height: number; 69 | 70 | //public static: boolean; 71 | public transition: ItemTransition; 72 | public scrollShrinkVal: number = 0; 73 | 74 | public shrinkStart = 0; 75 | public translateStart = 0; 76 | public moveStart = 0; 77 | 78 | constructor(public ele: HTMLElement) { 79 | 80 | if(ele.classList.contains("tabbar")) { 81 | this.type = ItemType.Tabbar; 82 | } else if(ele.tagName === "ION-NAVBAR") { 83 | this.type = ItemType.Navbar; 84 | } else { 85 | this.type = ItemType.Toolbar; 86 | } 87 | 88 | this.height = ele.offsetHeight; 89 | 90 | 91 | let shrinkable = ele.hasAttribute("scroll-hide-shrink"); 92 | let translatable = ele.hasAttribute("scroll-hide-translate"); 93 | 94 | if(shrinkable) { 95 | this.transition = ItemTransition.Shrink; 96 | var scrollShrinkVal = parseFloat(ele.getAttribute("scroll-hide-shrink")); 97 | if(isNaN(scrollShrinkVal)) scrollShrinkVal = 1; 98 | this.scrollShrinkVal = scrollShrinkVal; 99 | 100 | } else if(translatable) { 101 | this.transition = ItemTransition.Translate; 102 | 103 | } else { 104 | this.transition = ItemTransition.Static; 105 | 106 | } 107 | 108 | 109 | /*if(this.type === ItemType.Tabbar) { 110 | this.transition = ItemTransition.Shrink; 111 | this.scrollShrinkVal = 0.5; 112 | 113 | //this.transition = ItemTransition.Translate; 114 | 115 | //this.transition = ItemTransition.Static; 116 | }*/ 117 | } 118 | 119 | setY(shrinkStart: number, translateStart: number) { 120 | this.shrinkStart = shrinkStart; 121 | this.translateStart = translateStart; 122 | } 123 | 124 | setShrinkStart(shrinkStart: number) { 125 | this.shrinkStart = shrinkStart; 126 | } 127 | setTranslateStart(translateStart: number) { 128 | this.translateStart = translateStart; 129 | } 130 | setMoveStart(moveStart: number) { 131 | this.moveStart = moveStart; 132 | } 133 | 134 | } 135 | 136 | 137 | @Directive({ 138 | selector: '[scroll-hide]' // Attribute selector 139 | }) 140 | export class ScrollHide implements OnInit, OnDestroy { 141 | 142 | @Input() navCtrl: NavController; 143 | @Input() viewCtrl: ViewController; 144 | 145 | 146 | headerItems: Item[] = []; 147 | footerItems: Item[] = []; 148 | 149 | headerEle: HTMLElement; 150 | footerEle: HTMLElement; 151 | 152 | //--------------------------------- 153 | 154 | defaultDelay: number = 400 * 2; 155 | defaultDuration: number = 400; 156 | 157 | 158 | headerHeight: number; 159 | footerHeight: number; 160 | endY: number; 161 | 162 | defaultEnd: number = 0; 163 | y: number = 0; 164 | yPrev: number = 0; 165 | scrollTopPrev: number = 0; 166 | 167 | //--------------------------------- 168 | 169 | 170 | constructor(private content: Content, 171 | private renderer: Renderer, 172 | private zone: NgZone) {} 173 | 174 | 175 | ngOnInit() { 176 | this.content.fullscreen = true; 177 | 178 | 179 | //console.log("ngOnInit() > navCtrl:", this.navCtrl); 180 | //console.log("ngOnInit() > viewCtrl:", this.viewCtrl); 181 | 182 | 183 | this.viewCtrl.didEnter.subscribe(() => { 184 | //console.log("ScrollHide > viewCtrl.didEnter(), _tabsPlacement:", this.content._tabsPlacement); 185 | console.log("init()"); 186 | 187 | this.init(); 188 | 189 | }); 190 | this.viewCtrl.willLeave.subscribe(() => { 191 | console.log("resetStyle()"); 192 | this.resetStyle(); 193 | }); 194 | 195 | 196 | this.content.ionScroll.subscribe(event => { 197 | this.onContentScroll(event, false); 198 | }); 199 | this.content.ionScrollEnd.subscribe(event => { 200 | console.log("ionScrollEnd!"); 201 | this.onContentScroll(event, true); 202 | }); 203 | 204 | 205 | 206 | let win: any = window; 207 | win.temp = win.temp || {}; 208 | win.temp.content = this.content; 209 | win.temp.zone = this.zone; 210 | } 211 | 212 | ngOnDestroy() { 213 | console.log("ngOnDestroy()"); 214 | } 215 | 216 | private init() { 217 | this.y = 0; 218 | this.yPrev = 0; 219 | this.scrollTopPrev = this.content.scrollTop; 220 | 221 | 222 | 223 | this.content.resize(); 224 | 225 | 226 | let contentEle = this.content.getNativeElement(); 227 | let headerEle = contentEle.parentElement.querySelector("ion-header"); 228 | let footerEle = contentEle.parentElement.querySelector("ion-footer"); 229 | 230 | if(headerEle) { 231 | this.headerItems = $(headerEle) 232 | .children() 233 | .toArray().map(ele => new Item(ele)); 234 | } else { 235 | this.headerItems = []; 236 | } 237 | 238 | if(footerEle) { 239 | this.footerItems = $(footerEle) 240 | .children() 241 | .toArray().map(ele => new Item(ele)); 242 | } else { 243 | this.footerItems = []; 244 | } 245 | 246 | 247 | 248 | 249 | let hasTabbar = (this.content._tabs !== null); 250 | if(hasTabbar) { 251 | //this.tabbarEle = this.content._tabs._tabbar.nativeElement; 252 | let tabbarEle = ( this.content._tabs)._tabbar.nativeElement; 253 | 254 | if(this.content._tabsPlacement === "bottom") { 255 | this.footerItems.push(new Item(tabbarEle)); 256 | } else { 257 | this.headerItems.push(new Item(tabbarEle)); 258 | } 259 | } 260 | 261 | 262 | this.headerItems = this.headerItems.reverse(); 263 | this.footerItems = this.footerItems.reverse(); 264 | 265 | 266 | 267 | 268 | var headerHeight = 0, 269 | footerHeight = 0; 270 | 271 | console.group("headerItems") 272 | 273 | var shrinkableHeightSum = 0; 274 | this.headerItems.forEach((item, index) => { 275 | headerHeight += item.ele.offsetHeight; 276 | 277 | // Navbar would not be raised without setting position to relative 278 | if(item.type === ItemType.Navbar) { 279 | $(item.ele).css("position", "relative") 280 | } 281 | 282 | 283 | // Prevent overlapping of bottom element 284 | if(item.type === ItemType.Tabbar) { 285 | $(item.ele).css("overflow", "hidden"); 286 | $(item.ele).children().css("align-self", "flex-end"); 287 | } else { 288 | $(item.ele).css("z-index", index + 1); 289 | } 290 | 291 | 292 | // For shrinking 293 | //$(item.ele).css("padding-top", "0"); 294 | //$(item.ele).css("padding-bottom", "0"); 295 | 296 | 297 | switch(item.transition) { 298 | case ItemTransition.Shrink: 299 | let shrinkHeight = (item.height * item.scrollShrinkVal); 300 | item.setShrinkStart(shrinkableHeightSum); 301 | item.setTranslateStart(shrinkableHeightSum + shrinkHeight); 302 | item.setMoveStart(shrinkableHeightSum + shrinkHeight); 303 | shrinkableHeightSum += shrinkHeight; 304 | break; 305 | 306 | case ItemTransition.Translate: 307 | item.setShrinkStart(shrinkableHeightSum); 308 | item.setTranslateStart(shrinkableHeightSum); 309 | item.setMoveStart(shrinkableHeightSum + item.height); 310 | shrinkableHeightSum += item.height; 311 | break; 312 | 313 | case ItemTransition.Static: 314 | item.setShrinkStart(shrinkableHeightSum); 315 | item.setTranslateStart(shrinkableHeightSum); 316 | item.setMoveStart(shrinkableHeightSum); 317 | break; 318 | } 319 | 320 | console.log(item, item.ele.offsetHeight); 321 | }); 322 | this.headerHeight = headerHeight; 323 | this.defaultEnd = this.headerHeight * 2; 324 | this.endY = shrinkableHeightSum; 325 | 326 | console.groupEnd(); 327 | 328 | 329 | console.group("footerItems") 330 | this.footerItems.forEach(item => { 331 | footerHeight += item.ele.offsetHeight; 332 | console.log(item.ele, item.ele.offsetHeight); 333 | }); 334 | this.footerHeight = footerHeight; 335 | console.groupEnd(); 336 | 337 | console.log("headerHeight:", headerHeight, ", footerHeight:", footerHeight, ", endY:", this.endY); 338 | 339 | 340 | /* 341 | ion-header { 342 | pointer-events: none; 343 | } 344 | 345 | ion-header > * { 346 | pointer-events: all; 347 | }*/ 348 | 349 | if(headerEle) { 350 | $(headerEle).css("pointer-events", "none"); 351 | $(headerEle).children().css("pointer-events", "all"); 352 | } 353 | if(footerEle) { 354 | $(footerEle).css("pointer-events", "none"); 355 | $(footerEle).children().css("pointer-events", "all"); 356 | } 357 | 358 | this.headerEle = headerEle; 359 | this.footerEle = footerEle; 360 | } 361 | 362 | 363 | private resetStyle() { 364 | this.headerItems.forEach((item, index) => { 365 | 366 | if(item.type === ItemType.Navbar) { 367 | $(item.ele).css("position", ""); 368 | } 369 | 370 | if(item.type === ItemType.Tabbar) { 371 | $(item.ele).css("overflow", ""); 372 | $(item.ele).children().css("align-self", "center"); 373 | } else { 374 | $(item.ele).css("z-index", ""); 375 | } 376 | 377 | }); 378 | 379 | if(this.headerEle) { 380 | $(this.headerEle).css("pointer-events", "none"); 381 | $(this.headerEle).children().css("pointer-events", "all"); 382 | } 383 | if(this.footerEle) { 384 | $(this.footerEle).css("pointer-events", "none"); 385 | $(this.footerEle).children().css("pointer-events", "all"); 386 | } 387 | 388 | 389 | // Header Items 390 | this.headerItems.forEach((item) => { 391 | 392 | $(item.ele).css("height", ""); 393 | $(item.ele).css("display", ""); 394 | $(item.ele).css("webkitTransform", ""); 395 | 396 | if(item.transition === ItemTransition.Translate) { 397 | $(item.ele).children().not(".toolbar-background") 398 | .each((index, navbarChild) => { 399 | $(navbarChild).css("opacity", ""); 400 | }); 401 | } 402 | }); 403 | 404 | 405 | // Footer Items 406 | this.footerItems.forEach((item) => { 407 | $(item.ele).css("webkitTransform", ""); 408 | }); 409 | } 410 | 411 | 412 | 413 | 414 | 415 | 416 | hasScrollToBottomBefore = false; 417 | 418 | 419 | //@HostListener("ionScroll", ["$event", "false"]) 420 | onContentScroll(event: ScrollEvent, isMoveEnd: boolean) { 421 | //console.log("ionScroll", event); //{ contentHeight: event.contentHeight, scrollHeight: event.scrollHeight }); 422 | //this.checkTabsPlacment(); 423 | 424 | /*if(!this.initiated) { 425 | this.initiated = true; 426 | this.init(); 427 | this.content.resize(); 428 | }*/ 429 | 430 | 431 | let maxScrollTop = event.scrollHeight - (event.contentTop + event.contentHeight + event.contentBottom); 432 | 433 | 434 | 435 | var duration = 0; 436 | let scrollTopDiff = event.scrollTop - this.scrollTopPrev; 437 | 438 | let y = (event.scrollTop >= 0) ? constrain(this.yPrev + scrollTopDiff, 0, this.defaultEnd) : 0; 439 | 440 | 441 | 442 | //if we are at the bottom, animate the header/tabs back in 443 | if (event.scrollTop >= maxScrollTop - this.footerHeight) { 444 | 445 | if(this.hasScrollToBottomBefore) { 446 | y = 0; 447 | duration = this.defaultDuration; 448 | } 449 | 450 | if(isMoveEnd) { 451 | this.hasScrollToBottomBefore = true; 452 | this.content.setScrollElementStyle("padding-bottom", (this.content._pBottom + this.footerHeight) + "px"); 453 | } 454 | 455 | } else { 456 | this.hasScrollToBottomBefore = false; 457 | this.content.setScrollElementStyle("padding-bottom", (this.content._pBottom) + "px"); 458 | } 459 | 460 | 461 | console.log({ 462 | y: y, 463 | endY: this.endY, 464 | 465 | scrollTop: event.scrollTop, 466 | maxScrollTop: maxScrollTop, 467 | footerHeight: this.footerHeight, 468 | }); 469 | 470 | 471 | 472 | 473 | //if previous and current y are the same, no need to continue 474 | if (this.yPrev !== y) { 475 | //this.modifyDom(y, duration, event.domWrite); 476 | 477 | let yDiff = y - this.yPrev; 478 | if(-30 < yDiff && yDiff < 30) { 479 | this.modifyDom(y, duration, event.domWrite); 480 | } else { 481 | this.modifyDom(y, 200, event.domWrite); 482 | } 483 | } 484 | 485 | //console.log({ y: y, scrollTop: event.scrollTop, maxScrollTop: maxScrollTop}); 486 | 487 | 488 | this.yPrev = y; 489 | this.scrollTopPrev = event.scrollTop; 490 | } 491 | 492 | 493 | 494 | private modifyDom(y: number, duration: number, dowWrite: DomWrite) { 495 | dowWrite(() => { 496 | y = constrain(y, 0, this.endY); 497 | 498 | 499 | console.group(); 500 | 501 | 502 | // Header Items 503 | this.headerItems.forEach((item) => { 504 | 505 | //let dy = constrain((y - item.shrinkStart), 0, Infinity); 506 | //let val = constrain((dy / item.height), 0, 1); 507 | 508 | if(item.type === ItemType.Tabbar) { 509 | let dy_shrink = constrain((y - item.shrinkStart), 0, (item.translateStart - item.shrinkStart)); 510 | let dy_translate = constrain((y - item.translateStart), 0, (item.moveStart - item.translateStart)); 511 | let dy_move = constrain((y - item.moveStart), 0, Infinity); 512 | 513 | /*let height = this.content._tabbarHeight * (1 - val); 514 | if(height >= 0) { 515 | $(item.ele).css("height", height + "px"); 516 | $(item.ele).css("display", ""); 517 | } else { 518 | $(item.ele).css("display", "none"); 519 | }*/ 520 | if(y <= item.translateStart) { 521 | // Shrinking 522 | 523 | let val = constrain((dy_shrink / item.height), 0, 1); 524 | $(item.ele).css({ 525 | height: item.height - dy_shrink, 526 | //minHeight: item.height - dy_shrink, 527 | //marginBottom: dy_shrink, 528 | }) 529 | this.translateElementY(item.ele, 0, duration); 530 | 531 | } else if(y <= item.moveStart){ 532 | // Translating 533 | 534 | let val = constrain((dy_translate / item.height), 0, 1); 535 | $(item.ele).css({ 536 | height: item.height - dy_translate, 537 | //minHeight: item.height - (item.translateStart - item.shrinkStart), 538 | //marginBottom: (item.translateStart - item.shrinkStart), 539 | }) 540 | this.translateElementY(item.ele, 0, duration); 541 | 542 | } else { 543 | // Moving 544 | 545 | $(item.ele).css({ 546 | height: item.height - Math.max((item.translateStart - item.shrinkStart), (item.moveStart - item.translateStart)), 547 | //minHeight: item.height - (item.translateStart - item.shrinkStart), 548 | //marginBottom: (item.translateStart - item.shrinkStart), 549 | }) 550 | this.translateElementY(item.ele, -dy_move, duration); 551 | } 552 | 553 | } else { 554 | 555 | let dy_shrink = constrain((y - item.shrinkStart), 0, (item.translateStart - item.shrinkStart)); 556 | let dy_translate = constrain((y - item.translateStart), 0, (item.moveStart - item.translateStart)); 557 | let dy_move = constrain((y - item.moveStart), 0, Infinity); 558 | 559 | //var val = constrain((dy / item.height), 0, 1); 560 | 561 | //this.translateElementY(item.ele, -dy, duration); 562 | //if(item.scrollShrinkVal === 0.3) 563 | console.log(y, "shrink:", dy_shrink, "tranalate:", dy_translate); 564 | 565 | /*if(y <= item.translateStart) { 566 | // Shrinking 567 | 568 | let val = constrain((dy_shrink / item.height), 0, 1); 569 | $(item.ele).css({ 570 | height: item.height - dy_shrink, 571 | minHeight: item.height - dy_shrink, 572 | marginBottom: dy_shrink, 573 | }) 574 | this.translateElementY(item.ele, 0, duration); 575 | 576 | } else { 577 | // Translating 578 | 579 | //this.translateElementY(item.ele, -dy, duration); 580 | let val = constrain((dy_translate / item.height), 0, 1); 581 | 582 | $(item.ele).css({ 583 | height: item.height - (item.translateStart - item.shrinkStart), 584 | minHeight: item.height - (item.translateStart - item.shrinkStart), 585 | marginBottom: (item.translateStart - item.shrinkStart), 586 | }) 587 | this.translateElementY(item.ele, -dy_translate, duration); 588 | }*/ 589 | 590 | if(y <= item.translateStart) { 591 | // Shrinking 592 | 593 | let val = constrain((dy_shrink / item.height), 0, 1); 594 | $(item.ele).css({ 595 | height: item.height - dy_shrink, 596 | minHeight: item.height - dy_shrink, 597 | marginBottom: dy_shrink, 598 | }) 599 | this.translateElementY(item.ele, 0, duration); 600 | 601 | } else if(y <= item.moveStart) { 602 | // Translating 603 | 604 | //this.translateElementY(item.ele, -dy, duration); 605 | let val = constrain((dy_translate / item.height), 0, 1); 606 | 607 | $(item.ele).css({ 608 | height: item.height - (item.translateStart - item.shrinkStart), 609 | minHeight: item.height - (item.translateStart - item.shrinkStart), 610 | marginBottom: (item.translateStart - item.shrinkStart), 611 | }) 612 | this.translateElementY(item.ele, -dy_translate, duration); 613 | 614 | } else { 615 | // Moving 616 | 617 | let val = constrain((dy_move / item.height), 0, 1); 618 | 619 | let maxPrevVal = Math.max((item.translateStart - item.shrinkStart), (item.moveStart - item.translateStart)); 620 | $(item.ele).css({ 621 | height: item.height - maxPrevVal, 622 | minHeight: item.height - maxPrevVal, 623 | marginBottom: maxPrevVal, 624 | }) 625 | this.translateElementY(item.ele, -dy_move, duration); 626 | } 627 | } 628 | /*if(item.shrinkable) { 629 | //let fadeAmt = constrain(1 - (dy / item.ele.offsetHeight), 0, 1); 630 | let fadeAmt = 1 - val; 631 | 632 | $(item.ele).children().not(".toolbar-background") 633 | .each((index, navbarChild) => { 634 | $(navbarChild).css("opacity", fadeAmt); 635 | }); 636 | }*/ 637 | }); 638 | console.groupEnd(); 639 | 640 | 641 | // Footer Items 642 | this.footerItems.forEach((item) => { 643 | this.translateElementY(item.ele, y, duration); 644 | }); 645 | 646 | 647 | // Content 648 | /*if(y <= this.endY) { 649 | this.content.setScrollElementStyle("margin-top", (this.headerHeight - y) + "px"); 650 | } else { 651 | this.content.setScrollElementStyle("margin-top", (this.headerHeight - this.endY) + "px"); 652 | } 653 | 654 | if(y <= this.footerHeight) { 655 | this.content.setScrollElementStyle("margin-bottom", (this.footerHeight - y) + "px"); 656 | }*/ 657 | }); 658 | } 659 | 660 | private translateElementY(ele: HTMLElement, y: number, duration: number) { 661 | this.renderer.setElementStyle(ele, "webkitTransform", `translate3d(0, ${y}px,0)`); 662 | 663 | if (duration && !ele.style.transitionDuration) { 664 | ele.style.transitionDuration = duration + "ms"; 665 | setTimeout(() => { 666 | ele.style.transitionDuration = ""; 667 | }, this.defaultDelay); 668 | } 669 | } 670 | } 671 | -------------------------------------------------------------------------------- /src/components/scroll-hide/dev/scroll-hide-v3.0.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Directive, 3 | ElementRef, NgZone, HostListener, Input, Renderer, 4 | OnInit, OnDestroy, 5 | } from '@angular/core'; 6 | 7 | import { Content, ScrollEvent, NavController, ViewController } from 'ionic-angular' 8 | 9 | 10 | import * as $ from 'jquery' 11 | 12 | 13 | /* 14 | 15 | 16 | ion-header { 17 | pointer-events: none; 18 | } 19 | ion-header > * { 20 | pointer-events: all; 21 | } 22 | 23 | 24 | 25 | 26 | ion-header > ion-navbar { 27 | position: relative; 28 | } 29 | 30 | 31 | 32 | ion-tabs > .tabbar { 33 | overflow: hidden; 34 | } 35 | ion-tabs > .tabbar > *{ 36 | align-self: flex-end; 37 | } 38 | 39 | */ 40 | 41 | 42 | type DomWrite = (fn: (timeStamp?: number) => void, ctx?: any) => void; 43 | 44 | 45 | function constrain(val: number, min: number, max: number) { 46 | return Math.min(max, Math.max(val, min)); 47 | } 48 | 49 | 50 | 51 | 52 | 53 | enum ItemType { Navbar, Toolbar, Tabbar } 54 | 55 | enum TransitionType { Static, Translate, Shrink } 56 | interface Transition { 57 | type: TransitionType; 58 | start: number; 59 | end: number; 60 | interval: number; 61 | } 62 | 63 | /* 64 | * startY EndY 65 | * shrinkStart TransitionStart MoveStart 66 | * | ------------- | ---------------- | ----------> 67 | * Shrink Translate 68 | * 69 | * 70 | * transitionStart transitionEnd 71 | * | ------------- | ----------> 72 | * Shrink 73 | * 74 | * transitionStart transitionEnd 75 | * | ------------- | ----------> 76 | * Translate 77 | * 78 | */ 79 | class Item { 80 | public type: ItemType; 81 | public height: number; 82 | //public paddingTop: number; 83 | //public paddingBottom: number; 84 | 85 | //public static: boolean; 86 | public transition: Transition = { type: 0, start: 0, end: 0, interval: 0 }; 87 | public scrollShrinkVal: number = 0; 88 | 89 | //public transitionStart = 0; 90 | //public transitionEnd = 0; 91 | //public transitionInterval = 0; 92 | 93 | constructor(public ele: HTMLElement) { 94 | 95 | if(ele.classList.contains("tabbar")) { 96 | this.type = ItemType.Tabbar; 97 | } else if(ele.tagName === "ION-NAVBAR") { 98 | this.type = ItemType.Navbar; 99 | } else { 100 | this.type = ItemType.Toolbar; 101 | } 102 | 103 | this.height = ele.offsetHeight; 104 | 105 | 106 | let shrinkable = ele.hasAttribute("scroll-hide-shrink"); 107 | let translatable = ele.hasAttribute("scroll-hide-translate"); 108 | 109 | if(shrinkable) { 110 | this.transition.type = TransitionType.Shrink; 111 | var scrollShrinkVal = parseFloat(ele.getAttribute("scroll-hide-shrink")); 112 | if(isNaN(scrollShrinkVal)) scrollShrinkVal = 1; 113 | this.scrollShrinkVal = scrollShrinkVal; 114 | 115 | } else if(translatable) { 116 | this.transition.type = TransitionType.Translate; 117 | 118 | } else { 119 | this.transition.type = TransitionType.Static; 120 | 121 | } 122 | 123 | 124 | if(this.type === ItemType.Tabbar) { 125 | this.transition.type = TransitionType.Shrink; 126 | this.scrollShrinkVal = 0.5; 127 | 128 | //this.transition = ItemTransition.Translate; 129 | 130 | //this.transition = ItemTransition.Static; 131 | } 132 | } 133 | 134 | 135 | 136 | /*setTransitionStart(transitionStart: number) { 137 | this.transitionStart = transitionStart; 138 | } 139 | setTransitionEnd(transitionEnd: number) { 140 | this.transitionEnd = transitionEnd; 141 | } 142 | setTransitionInterval(transitionInterval: number) { 143 | this.transitionInterval = transitionInterval; 144 | }*/ 145 | 146 | } 147 | 148 | 149 | @Directive({ 150 | selector: '[scroll-hide]' // Attribute selector 151 | }) 152 | export class ScrollHide implements OnInit, OnDestroy { 153 | 154 | @Input() navCtrl: NavController; 155 | @Input() viewCtrl: ViewController; 156 | 157 | 158 | headerItems: Item[] = []; 159 | footerItems: Item[] = []; 160 | 161 | headerEle: HTMLElement; 162 | footerEle: HTMLElement; 163 | 164 | //--------------------------------- 165 | 166 | defaultDelay: number = 400 * 2; 167 | defaultDuration: number = 400; 168 | 169 | 170 | headerHeight: number; 171 | footerHeight: number; 172 | endY: number; 173 | 174 | defaultEnd: number = 0; 175 | y: number = 0; 176 | yPrev: number = 0; 177 | scrollTopPrev: number = 0; 178 | 179 | //--------------------------------- 180 | 181 | 182 | constructor(private content: Content, 183 | private renderer: Renderer, 184 | private zone: NgZone) {} 185 | 186 | 187 | ngOnInit() { 188 | this.content.fullscreen = true; 189 | 190 | 191 | //console.log("ngOnInit() > navCtrl:", this.navCtrl); 192 | //console.log("ngOnInit() > viewCtrl:", this.viewCtrl); 193 | 194 | 195 | this.viewCtrl.didEnter.subscribe(() => { 196 | //console.log("ScrollHide > viewCtrl.didEnter(), _tabsPlacement:", this.content._tabsPlacement); 197 | console.log("init()"); 198 | 199 | this.init(); 200 | 201 | }); 202 | this.viewCtrl.willLeave.subscribe(() => { 203 | console.log("resetStyle()"); 204 | this.resetStyle(); 205 | }); 206 | 207 | 208 | this.content.ionScroll.subscribe(event => { 209 | this.onContentScroll(event, false); 210 | }); 211 | this.content.ionScrollEnd.subscribe(event => { 212 | console.log("ionScrollEnd!"); 213 | this.onContentScroll(event, true); 214 | }); 215 | 216 | 217 | 218 | let win: any = window; 219 | win.temp = win.temp || {}; 220 | win.temp.content = this.content; 221 | win.temp.zone = this.zone; 222 | } 223 | 224 | ngOnDestroy() { 225 | console.log("ngOnDestroy()"); 226 | } 227 | 228 | 229 | 230 | private init() { 231 | this.y = 0; 232 | this.yPrev = 0; 233 | this.scrollTopPrev = this.content.scrollTop; 234 | 235 | 236 | 237 | this.content.resize(); 238 | 239 | 240 | let contentEle = this.content.getNativeElement(); 241 | let headerEle = contentEle.parentElement.querySelector("ion-header"); 242 | let footerEle = contentEle.parentElement.querySelector("ion-footer"); 243 | 244 | if(headerEle) { 245 | this.headerItems = $(headerEle) 246 | .children() 247 | .toArray().map(ele => new Item(ele)); 248 | } else { 249 | this.headerItems = []; 250 | } 251 | 252 | if(footerEle) { 253 | this.footerItems = $(footerEle) 254 | .children() 255 | .toArray().map(ele => new Item(ele)); 256 | } else { 257 | this.footerItems = []; 258 | } 259 | 260 | 261 | 262 | 263 | let hasTabbar = (this.content._tabs !== null); 264 | if(hasTabbar) { 265 | //this.tabbarEle = this.content._tabs._tabbar.nativeElement; 266 | let tabbarEle = ( this.content._tabs)._tabbar.nativeElement; 267 | 268 | if(this.content._tabsPlacement === "bottom") { 269 | this.footerItems.push(new Item(tabbarEle)); 270 | } else { 271 | this.headerItems.push(new Item(tabbarEle)); 272 | } 273 | } 274 | 275 | 276 | this.headerItems = this.headerItems.reverse(); 277 | this.footerItems = this.footerItems.reverse(); 278 | 279 | 280 | 281 | 282 | var headerHeight = 0, 283 | footerHeight = 0; 284 | 285 | console.group("headerItems") 286 | 287 | var shrinkableHeightSum = 0; 288 | this.headerItems.forEach((item, index) => { 289 | headerHeight += item.ele.offsetHeight; 290 | 291 | // Navbar would not be raised without setting position to relative 292 | if(item.type === ItemType.Navbar) { 293 | $(item.ele).css("position", "relative") 294 | } 295 | 296 | 297 | // Prevent overlapping of bottom element 298 | if(item.type === ItemType.Tabbar) { 299 | $(item.ele).css("overflow", "hidden"); 300 | $(item.ele).children().css("align-self", "flex-end"); 301 | } else { 302 | $(item.ele).css("z-index", index + 1); 303 | } 304 | 305 | 306 | // For shrinking 307 | //$(item.ele).css("padding-top", "0"); 308 | //$(item.ele).css("padding-bottom", "0"); 309 | 310 | 311 | $(item.ele).css("min-height", "0"); 312 | $(item.ele).css("height", item.height + "px"); 313 | 314 | 315 | switch(item.transition.type) { 316 | 317 | case TransitionType.Shrink: 318 | 319 | let shrinkHeight = (item.height * item.scrollShrinkVal); 320 | item.transition.start = (shrinkableHeightSum); 321 | item.transition.end = (shrinkableHeightSum + shrinkHeight); 322 | item.transition.interval = (shrinkHeight); 323 | shrinkableHeightSum += (shrinkHeight); 324 | break; 325 | 326 | case TransitionType.Translate: 327 | 328 | item.transition.start = (shrinkableHeightSum); 329 | item.transition.end = (shrinkableHeightSum + item.height); 330 | item.transition.interval = (item.height); 331 | shrinkableHeightSum += (item.height); 332 | break; 333 | 334 | case TransitionType.Static: 335 | 336 | item.transition.start = (shrinkableHeightSum); 337 | item.transition.end = (shrinkableHeightSum); 338 | item.transition.interval = (0); 339 | break; 340 | } 341 | 342 | console.log(item, item.ele.offsetHeight); 343 | }); 344 | this.headerHeight = headerHeight; 345 | this.defaultEnd = this.headerHeight * 2; 346 | this.endY = shrinkableHeightSum; 347 | 348 | console.groupEnd(); 349 | 350 | 351 | console.group("footerItems") 352 | this.footerItems.forEach(item => { 353 | footerHeight += item.ele.offsetHeight; 354 | console.log(item.ele, item.ele.offsetHeight); 355 | }); 356 | this.footerHeight = footerHeight; 357 | console.groupEnd(); 358 | 359 | console.log("headerHeight:", headerHeight, ", footerHeight:", footerHeight, ", endY:", this.endY); 360 | 361 | 362 | /* 363 | ion-header { 364 | pointer-events: none; 365 | } 366 | 367 | ion-header > * { 368 | pointer-events: all; 369 | }*/ 370 | 371 | if(headerEle) { 372 | $(headerEle).css("pointer-events", "none"); 373 | $(headerEle).children().css("pointer-events", "all"); 374 | } 375 | if(footerEle) { 376 | $(footerEle).css("pointer-events", "none"); 377 | $(footerEle).children().css("pointer-events", "all"); 378 | } 379 | 380 | this.headerEle = headerEle; 381 | this.footerEle = footerEle; 382 | 383 | 384 | 385 | } 386 | 387 | 388 | private resetStyle() { 389 | this.headerItems.forEach((item, index) => { 390 | 391 | if(item.type === ItemType.Navbar) { 392 | $(item.ele).css("position", ""); 393 | } 394 | 395 | if(item.type === ItemType.Tabbar) { 396 | $(item.ele).css("overflow", ""); 397 | $(item.ele).children().css("align-self", "center"); 398 | } else { 399 | $(item.ele).css("z-index", ""); 400 | } 401 | 402 | }); 403 | 404 | if(this.headerEle) { 405 | $(this.headerEle).css("pointer-events", "none"); 406 | $(this.headerEle).children().css("pointer-events", "all"); 407 | } 408 | if(this.footerEle) { 409 | $(this.footerEle).css("pointer-events", "none"); 410 | $(this.footerEle).children().css("pointer-events", "all"); 411 | } 412 | 413 | 414 | // Header Items 415 | this.headerItems.forEach((item) => { 416 | 417 | $(item.ele).css("height", ""); 418 | $(item.ele).css("display", ""); 419 | $(item.ele).css("webkitTransform", ""); 420 | 421 | if(item.transition.type === TransitionType.Translate) { 422 | $(item.ele).children().not(".toolbar-background") 423 | .each((index, navbarChild) => { 424 | $(navbarChild).css("opacity", ""); 425 | }); 426 | } 427 | }); 428 | 429 | 430 | // Footer Items 431 | this.footerItems.forEach((item) => { 432 | $(item.ele).css("webkitTransform", ""); 433 | }); 434 | } 435 | 436 | 437 | 438 | 439 | 440 | 441 | hasScrollToBottomBefore = false; 442 | 443 | 444 | //@HostListener("ionScroll", ["$event", "false"]) 445 | onContentScroll(event: ScrollEvent, isMoveEnd: boolean) { 446 | 447 | let maxScrollTop = event.scrollHeight - (event.contentTop + event.contentHeight + event.contentBottom); 448 | 449 | 450 | 451 | var duration = 0; 452 | let scrollTopDiff = event.scrollTop - this.scrollTopPrev; 453 | 454 | let y = (event.scrollTop >= 0) ? constrain(this.yPrev + scrollTopDiff, 0, this.defaultEnd) : 0; 455 | 456 | 457 | 458 | //if we are at the bottom, animate the header/tabs back in 459 | if (event.scrollTop >= maxScrollTop - this.footerHeight) { 460 | 461 | if(this.hasScrollToBottomBefore) { 462 | y = 0; 463 | duration = this.defaultDuration; 464 | } 465 | 466 | if(isMoveEnd) { 467 | this.hasScrollToBottomBefore = true; 468 | this.content.setScrollElementStyle("padding-bottom", (this.content._pBottom + this.footerHeight) + "px"); 469 | } 470 | 471 | } else { 472 | this.hasScrollToBottomBefore = false; 473 | this.content.setScrollElementStyle("padding-bottom", (this.content._pBottom) + "px"); 474 | } 475 | 476 | 477 | console.log({ 478 | y: y, 479 | endY: this.endY, 480 | 481 | scrollTop: event.scrollTop, 482 | maxScrollTop: maxScrollTop, 483 | footerHeight: this.footerHeight, 484 | }); 485 | 486 | 487 | 488 | 489 | //if previous and current y are the same, no need to continue 490 | if (this.yPrev !== y) { 491 | //this.modifyDom(y, duration, event.domWrite); 492 | 493 | let yDiff = y - this.yPrev; 494 | if(-30 < yDiff && yDiff < 30) { 495 | this.modifyDom(y, duration, event.domWrite); 496 | } else { 497 | this.modifyDom(y, 50, event.domWrite); 498 | } 499 | } 500 | 501 | //console.log({ y: y, scrollTop: event.scrollTop, maxScrollTop: maxScrollTop}); 502 | 503 | 504 | this.yPrev = y; 505 | this.scrollTopPrev = event.scrollTop; 506 | } 507 | 508 | 509 | 510 | 511 | 512 | private modifyDom(y: number, duration: number, dowWrite: DomWrite) { 513 | dowWrite(() => { 514 | y = constrain(y, 0, this.endY); 515 | 516 | 517 | console.group(); 518 | 519 | 520 | // Header Items 521 | this.headerItems.forEach((item) => { 522 | 523 | let dy_transit = constrain((y - item.transition.start), 0, item.transition.interval); 524 | let dy_move = constrain((y - item.transition.end), 0, Infinity); 525 | 526 | console.log(y, "dy_transit:", dy_transit, "dy_move:", dy_move, TransitionType[item.transition.type]); 527 | 528 | if(item.type === ItemType.Tabbar) { 529 | //let dy_transit = constrain((y - item.transition.start), 0, item.transition.interval); 530 | //let dy_move = constrain((y - item.transition.end), 0, Infinity); 531 | 532 | 533 | if(y <= item.transition.end) { 534 | // Transiting 535 | 536 | if(item.transition.type === TransitionType.Shrink) { 537 | // Shrinking 538 | 539 | let val = constrain((dy_transit / item.height), 0, 1); 540 | $(item.ele).css({ 541 | height: item.height - dy_transit, 542 | //minHeight: item.height - dy_shrink, 543 | //marginBottom: dy_shrink, 544 | }) 545 | this.translateElementY(item.ele, 0, duration); 546 | 547 | } else { 548 | // Translating 549 | 550 | let val = constrain((dy_transit / item.height), 0, 1); 551 | $(item.ele).css({ 552 | height: item.height - dy_transit, 553 | //minHeight: item.height - (item.translateStart - item.shrinkStart), 554 | //marginBottom: (item.translateStart - item.shrinkStart), 555 | }) 556 | this.translateElementY(item.ele, 0, duration); 557 | } 558 | 559 | } else { 560 | // Moving 561 | 562 | $(item.ele).css({ 563 | height: item.height - item.transition.interval, 564 | //minHeight: item.height - (item.translateStart - item.shrinkStart), 565 | //marginBottom: (item.translateStart - item.shrinkStart), 566 | }) 567 | this.translateElementY(item.ele, -dy_move, duration); 568 | } 569 | 570 | } else { 571 | 572 | if(y <= item.transition.end) { 573 | // Transiting 574 | 575 | if(item.transition.type === TransitionType.Shrink) { 576 | // Shrinking 577 | 578 | let val = constrain((dy_transit / item.height), 0, 1); 579 | $(item.ele).css({ 580 | height: item.height - dy_transit, 581 | //minHeight: item.height - dy_transit, 582 | marginBottom: dy_transit, 583 | paddingTop: "", 584 | paddingBottom: "", 585 | }) 586 | this.translateElementY(item.ele, 0, duration); 587 | 588 | 589 | } else { 590 | // Translating 591 | 592 | let val = constrain((dy_transit / item.height), 0, 1); 593 | 594 | $(item.ele).css({ 595 | height: item.height, 596 | //minHeight: item.height, 597 | marginBottom: 0, 598 | paddingTop: "", 599 | paddingBottom: "", 600 | }) 601 | this.translateElementY(item.ele, -dy_transit, duration); 602 | } 603 | 604 | } else { 605 | // Moving 606 | 607 | let val = constrain((dy_move / item.height), 0, 1); 608 | 609 | $(item.ele).css({ 610 | height: item.height - item.transition.interval, 611 | //minHeight: item.height - item.transition.interval, 612 | marginBottom: item.transition.interval, 613 | paddingTop: 0, 614 | paddingBottom: 0, 615 | }) 616 | this.translateElementY(item.ele, -dy_move, duration); 617 | } 618 | 619 | } 620 | /*if(item.shrinkable) { 621 | //let fadeAmt = constrain(1 - (dy / item.ele.offsetHeight), 0, 1); 622 | let fadeAmt = 1 - val; 623 | 624 | $(item.ele).children().not(".toolbar-background") 625 | .each((index, navbarChild) => { 626 | $(navbarChild).css("opacity", fadeAmt); 627 | }); 628 | }*/ 629 | }); 630 | console.groupEnd(); 631 | 632 | 633 | // Footer Items 634 | this.footerItems.forEach((item) => { 635 | this.translateElementY(item.ele, y, duration); 636 | }); 637 | 638 | 639 | // Content 640 | /*if(y <= this.endY) { 641 | this.content.setScrollElementStyle("margin-top", (this.headerHeight - y) + "px"); 642 | } else { 643 | this.content.setScrollElementStyle("margin-top", (this.headerHeight - this.endY) + "px"); 644 | } 645 | 646 | if(y <= this.footerHeight) { 647 | this.content.setScrollElementStyle("margin-bottom", (this.footerHeight - y) + "px"); 648 | }*/ 649 | }); 650 | } 651 | 652 | private translateElementY(ele: HTMLElement, y: number, duration: number) { 653 | this.renderer.setElementStyle(ele, "webkitTransform", `translate3d(0, ${y}px,0)`); 654 | 655 | if (duration && !ele.style.transitionDuration) { 656 | ele.style.transitionDuration = duration + "ms"; 657 | setTimeout(() => { 658 | ele.style.transitionDuration = ""; 659 | }, this.defaultDelay); 660 | } 661 | } 662 | } 663 | -------------------------------------------------------------------------------- /src/components/scroll-hide/dev/scroll-hide-v3.1.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Directive, 3 | ElementRef, NgZone, HostListener, Input, Renderer, 4 | OnInit, OnDestroy, 5 | } from '@angular/core'; 6 | 7 | import { Content, ScrollEvent, NavController, ViewController } from 'ionic-angular' 8 | import { Subscription } from 'rxjs'; 9 | 10 | 11 | import * as $ from 'jquery' 12 | 13 | 14 | /* 15 | 16 | 17 | ion-header { 18 | pointer-events: none; 19 | } 20 | ion-header > * { 21 | pointer-events: all; 22 | } 23 | 24 | 25 | 26 | 27 | ion-header > ion-navbar { 28 | position: relative; 29 | } 30 | 31 | 32 | 33 | ion-tabs > .tabbar { 34 | overflow: hidden; 35 | } 36 | ion-tabs > .tabbar > *{ 37 | align-self: flex-end; 38 | } 39 | 40 | */ 41 | 42 | 43 | type DomWrite = (fn: (timeStamp?: number) => void, ctx?: any) => void; 44 | 45 | 46 | function constrain(val: number, min: number, max: number) { 47 | return Math.min(max, Math.max(val, min)); 48 | } 49 | 50 | 51 | 52 | 53 | 54 | enum ItemType { Navbar, Toolbar, Tabbar } 55 | 56 | enum TransitionType { Static, Translate, Shrink } 57 | interface Transition { 58 | type: TransitionType; 59 | start: number; 60 | end: number; 61 | interval: number; 62 | } 63 | 64 | /* 65 | * startY EndY 66 | * shrinkStart TransitionStart MoveStart 67 | * | ------------- | ---------------- | ----------> 68 | * Shrink Translate 69 | * 70 | * 71 | * transitionStart transitionEnd 72 | * | ------------- | ----------> 73 | * Shrink 74 | * 75 | * transitionStart transitionEnd 76 | * | ------------- | ----------> 77 | * Translate 78 | * 79 | */ 80 | class Item { 81 | public type: ItemType; 82 | public height: number; 83 | 84 | public transition: Transition = { type: 0, start: 0, end: 0, interval: 0 }; 85 | public scrollShrinkVal: number = 0; 86 | 87 | 88 | constructor(public ele: HTMLElement) { 89 | 90 | if(ele.classList.contains("tabbar")) { 91 | this.type = ItemType.Tabbar; 92 | } else if(ele.tagName === "ION-NAVBAR") { 93 | this.type = ItemType.Navbar; 94 | } else { 95 | this.type = ItemType.Toolbar; 96 | } 97 | 98 | this.height = ele.offsetHeight; 99 | 100 | 101 | let shrinkable = ele.hasAttribute("scroll-hide-shrink"); 102 | let translatable = ele.hasAttribute("scroll-hide-translate"); 103 | 104 | if(shrinkable) { 105 | this.transition.type = TransitionType.Shrink; 106 | var scrollShrinkVal = parseFloat(ele.getAttribute("scroll-hide-shrink")); 107 | if(isNaN(scrollShrinkVal)) scrollShrinkVal = 1; 108 | this.scrollShrinkVal = scrollShrinkVal; 109 | 110 | } else if(translatable) { 111 | this.transition.type = TransitionType.Translate; 112 | 113 | } else { 114 | this.transition.type = TransitionType.Static; 115 | 116 | } 117 | 118 | 119 | if(this.type === ItemType.Tabbar) { 120 | this.transition.type = TransitionType.Shrink; 121 | this.scrollShrinkVal = 0.5; 122 | 123 | //this.transition.type = TransitionType.Translate; 124 | 125 | //this.transition.type = TransitionType.Static; 126 | } 127 | } 128 | 129 | } 130 | 131 | 132 | @Directive({ 133 | selector: '[scroll-hide]' // Attribute selector 134 | }) 135 | export class ScrollHide implements OnInit, OnDestroy { 136 | 137 | @Input() navCtrl: NavController; 138 | @Input() viewCtrl: ViewController; 139 | 140 | 141 | headerItems: Item[] = []; 142 | footerItems: Item[] = []; 143 | 144 | headerEle: HTMLElement; 145 | footerEle: HTMLElement; 146 | 147 | //--------------------------------- 148 | 149 | defaultDelay: number = 400 * 2; 150 | defaultDuration: number = 400; 151 | 152 | 153 | headerHeight: number; 154 | footerHeight: number; 155 | endY: number; 156 | 157 | defaultEnd: number = 0; 158 | y: number = 0; 159 | yPrev: number = 0; 160 | scrollTopPrev: number = 0; 161 | 162 | 163 | initiated: boolean = false; 164 | viewEntered: boolean = false; 165 | 166 | //--------------------------------- 167 | 168 | subscriptions: Subscription[] = []; 169 | 170 | //--------------------------------- 171 | 172 | 173 | constructor(private content: Content, 174 | private renderer: Renderer, 175 | private zone: NgZone) {} 176 | 177 | 178 | 179 | ngOnInit() { 180 | this.content.fullscreen = true; 181 | 182 | 183 | //console.log("ngOnInit() > navCtrl:", this.navCtrl); 184 | //console.log("ngOnInit() > viewCtrl:", this.viewCtrl); 185 | 186 | 187 | 188 | this.subscriptions.push( 189 | this.viewCtrl.willEnter.subscribe(() => { 190 | if(this.initiated) { 191 | 192 | 193 | //this.appplyStoredTabbarStyle(); 194 | } 195 | }) 196 | ); 197 | this.subscriptions.push( 198 | this.viewCtrl.didEnter.subscribe(() => { 199 | //this.viewEntered = true; 200 | /*if(!this.initiated) { 201 | this.initiated = true; 202 | console.log("init()"); 203 | this.init(); 204 | } else { 205 | 206 | }*/ 207 | 208 | this.init(); 209 | }) 210 | ); 211 | 212 | /*(this.content._tabs).ionChange.subscribe(() => { 213 | console.log("ionChange > viewEntered:", this.viewEntered); 214 | if(this.viewEntered && this.cachedTabbarStayle) { 215 | //this.appplyStoredTabbarStyle(); 216 | if(this.content._tabsPlacement === "top") { 217 | let top = parseInt(this.cachedTabbarStayle.top); 218 | console.log("this.content._tabsPlacement === 'top' top:", top); 219 | this.content._tabs.setTabbarPosition(top, -1); 220 | } 221 | } 222 | });*/ 223 | 224 | 225 | this.subscriptions.push( 226 | this.viewCtrl.willLeave.subscribe(() => { 227 | //console.log("resetStyle()"); 228 | this.resetStyle(); 229 | 230 | //this.viewEntered = false; 231 | 232 | //this.storeTabbarStyle(); 233 | //this.resetTabbarStyle(); 234 | }) 235 | ); 236 | 237 | 238 | 239 | 240 | // Prevent multipe trigger of scrollEnd event 241 | 242 | //var startTimestamp: number = 0; 243 | var prevEndTimestep: number = 0; 244 | this.subscriptions.push( 245 | this.content.ionScrollStart.subscribe(event => { 246 | //console.log("ionScrollStart!", event); 247 | //startTimestamp = event.timeStamp; 248 | //console.log("ionScrollStart!", event.timeStamp); 249 | }) 250 | ); 251 | this.subscriptions.push( 252 | this.content.ionScroll.subscribe(event => { 253 | if(event.timeStamp > prevEndTimestep + 200) { // prevent multiple triggered of scroll event 254 | this.onContentScroll(event, false); 255 | //console.log("ionScroll!", event.timeStamp); 256 | } else { 257 | //console.log("fake --> ionScroll!", event.timeStamp); 258 | } 259 | //this.onContentScroll(event, false); 260 | }) 261 | ); 262 | this.subscriptions.push( 263 | this.content.ionScrollEnd.subscribe(event => { 264 | if(event.timeStamp > prevEndTimestep + 200) { 265 | this.onContentScroll(event, true); 266 | //console.log("ionScrollEnd!", event.timeStamp); 267 | } else { 268 | //console.log("fake --> ionScrollEnd!", event.timeStamp); 269 | } 270 | prevEndTimestep = event.timeStamp; 271 | }) 272 | ); 273 | 274 | 275 | 276 | let win: any = window; 277 | win.temp = win.temp || {}; 278 | win.temp.content = this.content; 279 | win.temp.zone = this.zone; 280 | win.temp.viewCtrl = this.viewCtrl; 281 | } 282 | 283 | ngOnDestroy() { 284 | console.log("ngOnDestroy()"); 285 | 286 | this.resetStyle(); 287 | 288 | 289 | this.navCtrl = null; 290 | this.viewCtrl = null; 291 | 292 | this.headerItems = null; 293 | this.footerItems = null; 294 | 295 | this.headerEle = null; 296 | this.footerEle = null; 297 | 298 | this.subscriptions.forEach(sub => sub.unsubscribe()); 299 | this.subscriptions = null; 300 | } 301 | 302 | 303 | 304 | private init() { 305 | this.y = 0; 306 | this.yPrev = 0; 307 | this.scrollTopPrev = this.content.scrollTop; 308 | 309 | 310 | 311 | this.content.resize(); 312 | 313 | 314 | let contentEle = this.content.getNativeElement(); 315 | let headerEle = contentEle.parentElement.querySelector("ion-header"); 316 | let footerEle = contentEle.parentElement.querySelector("ion-footer"); 317 | 318 | if(headerEle) { 319 | this.headerItems = $(headerEle) 320 | .children() 321 | .toArray().map(ele => new Item(ele)); 322 | } else { 323 | this.headerItems = []; 324 | } 325 | 326 | if(footerEle) { 327 | this.footerItems = $(footerEle) 328 | .children() 329 | .toArray().map(ele => new Item(ele)); 330 | } else { 331 | this.footerItems = []; 332 | } 333 | 334 | 335 | 336 | 337 | let hasTabbar = (this.content._tabs !== null); 338 | if(hasTabbar) { 339 | //this.tabbarEle = this.content._tabs._tabbar.nativeElement; 340 | let tabbarEle = ( this.content._tabs)._tabbar.nativeElement; 341 | 342 | if(this.content._tabsPlacement === "bottom") { 343 | this.footerItems.push(new Item(tabbarEle)); 344 | } else { 345 | this.headerItems.push(new Item(tabbarEle)); 346 | } 347 | } 348 | 349 | 350 | this.headerItems = this.headerItems.reverse(); 351 | this.footerItems = this.footerItems.reverse(); 352 | 353 | 354 | 355 | 356 | var headerHeight = 0, 357 | footerHeight = 0; 358 | 359 | console.group("headerItems") 360 | 361 | var shrinkableHeightSum = 0; 362 | this.headerItems.forEach((item, index) => { 363 | headerHeight += item.ele.offsetHeight; 364 | 365 | // Navbar would not be raised without setting position to relative 366 | if(item.type === ItemType.Navbar) { 367 | $(item.ele).css("position", "relative") 368 | } 369 | 370 | 371 | // Prevent overlapping of bottom element 372 | if(item.type === ItemType.Tabbar) { 373 | $(item.ele).css("overflow", "hidden"); 374 | $(item.ele).children().css("align-self", "flex-end"); 375 | } else { 376 | $(item.ele).css("z-index", index + 1); 377 | } 378 | 379 | 380 | // For shrinking 381 | //$(item.ele).css("padding-top", "0"); 382 | //$(item.ele).css("padding-bottom", "0"); 383 | 384 | 385 | $(item.ele).css("min-height", "0"); 386 | $(item.ele).css("height", item.height + "px"); 387 | 388 | 389 | switch(item.transition.type) { 390 | 391 | case TransitionType.Shrink: 392 | 393 | let shrinkHeight = (item.height * item.scrollShrinkVal); 394 | item.transition.start = (shrinkableHeightSum); 395 | item.transition.end = (shrinkableHeightSum + shrinkHeight); 396 | item.transition.interval = (shrinkHeight); 397 | shrinkableHeightSum += (shrinkHeight); 398 | break; 399 | 400 | case TransitionType.Translate: 401 | 402 | item.transition.start = (shrinkableHeightSum); 403 | item.transition.end = (shrinkableHeightSum + item.height); 404 | item.transition.interval = (item.height); 405 | shrinkableHeightSum += (item.height); 406 | break; 407 | 408 | case TransitionType.Static: 409 | 410 | item.transition.start = (shrinkableHeightSum); 411 | item.transition.end = (shrinkableHeightSum); 412 | item.transition.interval = (0); 413 | break; 414 | } 415 | 416 | console.log(item, item.ele.offsetHeight); 417 | }); 418 | this.headerHeight = headerHeight; 419 | this.defaultEnd = this.headerHeight * 2; 420 | this.endY = shrinkableHeightSum; 421 | 422 | console.groupEnd(); 423 | 424 | 425 | console.group("footerItems") 426 | this.footerItems.forEach(item => { 427 | footerHeight += item.ele.offsetHeight; 428 | console.log(item.ele, item.ele.offsetHeight); 429 | }); 430 | this.footerHeight = footerHeight; 431 | console.groupEnd(); 432 | 433 | console.log("headerHeight:", headerHeight, ", footerHeight:", footerHeight, ", endY:", this.endY); 434 | 435 | 436 | /* 437 | ion-header { 438 | pointer-events: none; 439 | } 440 | 441 | ion-header > * { 442 | pointer-events: all; 443 | }*/ 444 | 445 | if(headerEle) { 446 | $(headerEle).css("pointer-events", "none"); 447 | $(headerEle).children().css("pointer-events", "all"); 448 | } 449 | if(footerEle) { 450 | $(footerEle).css("pointer-events", "none"); 451 | $(footerEle).children().css("pointer-events", "all"); 452 | } 453 | 454 | this.headerEle = headerEle; 455 | this.footerEle = footerEle; 456 | 457 | 458 | 459 | } 460 | 461 | 462 | private resetStyle() { 463 | this.headerItems.forEach((item, index) => { 464 | 465 | if(item.type === ItemType.Navbar) { 466 | $(item.ele).css("position", ""); 467 | } 468 | 469 | if(item.type === ItemType.Tabbar) { 470 | $(item.ele).css("overflow", ""); 471 | $(item.ele).children().css("align-self", "center"); 472 | } else { 473 | $(item.ele).css("z-index", ""); 474 | } 475 | 476 | }); 477 | 478 | if(this.headerEle) { 479 | $(this.headerEle).css("pointer-events", "none"); 480 | $(this.headerEle).children().css("pointer-events", "all"); 481 | } 482 | if(this.footerEle) { 483 | $(this.footerEle).css("pointer-events", "none"); 484 | $(this.footerEle).children().css("pointer-events", "all"); 485 | } 486 | 487 | 488 | // Header Items 489 | this.headerItems.forEach((item) => { 490 | 491 | $(item.ele).css("height", ""); 492 | $(item.ele).css("min-height", ""); 493 | $(item.ele).css("webkitTransform", ""); 494 | 495 | 496 | if(item.type !== ItemType.Tabbar) { 497 | 498 | $(item.ele).css("marginBottom", ""); 499 | 500 | if(item.transition.type === TransitionType.Shrink) { 501 | // Shrinking 502 | $(item.ele).children().not(".toolbar-background, .toolbar-content") 503 | .each((index, navbarChild) => { 504 | $(navbarChild).css("transform", ""); 505 | }); 506 | $(item.ele).children(".toolbar-content").children() 507 | .each((index, navbarChild) => { 508 | $(navbarChild).css("transform", ""); 509 | }); 510 | 511 | } else { 512 | // Translating 513 | $(item.ele).children().not(".toolbar-background") 514 | .each((index, navbarChild) => { 515 | $(navbarChild).css("opacity", ""); 516 | }); 517 | } 518 | 519 | } 520 | }); 521 | 522 | 523 | // Footer Items 524 | this.footerItems.forEach((item) => { 525 | $(item.ele).css("webkitTransform", ""); 526 | }); 527 | 528 | 529 | // Content 530 | this.content.setScrollElementStyle("padding-bottom", (this.content._pBottom) + "px"); 531 | } 532 | 533 | 534 | cachedTabbarStayle: { 535 | top: string; 536 | height?: string; 537 | webkitTransform: string; 538 | }; 539 | private storeTabbarStyle() { 540 | console.log("storeTabbarStyle()"); 541 | 542 | this.headerItems.forEach((item) => { 543 | if(item.type === ItemType.Tabbar) { 544 | this.cachedTabbarStayle = { 545 | top: $(item.ele).css("top"), 546 | height: $(item.ele).css("height"), 547 | webkitTransform: $(item.ele).css("webkitTransform") 548 | }; 549 | } 550 | }); 551 | this.footerItems.forEach((item) => { 552 | if(item.type === ItemType.Tabbar) { 553 | this.cachedTabbarStayle = { 554 | top: $(item.ele).css("top"), 555 | webkitTransform: $(item.ele).css("webkitTransform") 556 | }; 557 | } 558 | }); 559 | console.log("this.cachedTabbarStayle:", this.cachedTabbarStayle); 560 | } 561 | private resetTabbarStyle() { 562 | console.log("resetTabbarStyle()"); 563 | 564 | this.headerItems.forEach((item) => { 565 | if(item.type === ItemType.Tabbar) { 566 | $(item.ele).css("overflow", ""); 567 | $(item.ele).children().css("align-self", "center"); 568 | 569 | $(item.ele).css("height", ""); 570 | $(item.ele).css("min-height", ""); 571 | $(item.ele).css("webkitTransform", ""); 572 | } 573 | }); 574 | this.footerItems.forEach((item) => { 575 | if(item.type === ItemType.Tabbar) { 576 | $(item.ele).css("webkitTransform", ""); 577 | } 578 | }); 579 | } 580 | private appplyStoredTabbarStyle() { 581 | console.log("appplyStoredTabbarStyle()"); 582 | 583 | this.headerItems.forEach((item) => { 584 | if(item.type === ItemType.Tabbar) { 585 | $(item.ele).css("overflow", "hidden"); 586 | $(item.ele).children().css("align-self", "flex-end"); 587 | $(item.ele).css("min-height", "0"); 588 | 589 | $(item.ele).css("top", this.cachedTabbarStayle.top); 590 | $(item.ele).css("height", this.cachedTabbarStayle.height); 591 | $(item.ele).css("webkitTransform", this.cachedTabbarStayle.webkitTransform); 592 | } 593 | }); 594 | this.footerItems.forEach((item) => { 595 | if(item.type === ItemType.Tabbar) { 596 | $(item.ele).css("top", this.cachedTabbarStayle.top); 597 | $(item.ele).css("webkitTransform", this.cachedTabbarStayle.webkitTransform); 598 | } 599 | }); 600 | } 601 | 602 | 603 | 604 | hasStopMoving = false; 605 | hasClearStopMoving = false; 606 | 607 | extendBottom = false; 608 | hasReachBottomBefore = false; 609 | hasBeyondMaxScrollTop = false; 610 | 611 | //@HostListener("ionScroll", ["$event", "false"]) 612 | onContentScroll(event: ScrollEvent, isMoveEnd: boolean) { 613 | 614 | //let maxScrollTop = event.scrollHeight - (event.contentTop + event.contentHeight + event.contentBottom); 615 | 616 | // For Ionic 3 617 | let maxScrollTop = this.content._scrollContent.nativeElement.scrollHeight - (event.contentTop + event.contentHeight + event.contentBottom) - (this.extendBottom ? this.footerHeight : 0); 618 | // For Ionic 2 619 | //let maxScrollTop = this.content._scrollEle.scrollHeight - (event.contentTop + event.contentHeight + event.contentBottom) - (this.extendBottom ? this.footerHeight : 0); // For ionic2 620 | 621 | 622 | 623 | var duration = 0; 624 | let scrollTopDiff = event.scrollTop - this.scrollTopPrev; 625 | 626 | let y = (event.scrollTop >= 0) ? constrain(this.yPrev + scrollTopDiff, 0, this.defaultEnd) : 0; 627 | 628 | 629 | //console.log(event.scrollTop, "maxScrollTop:", maxScrollTop, "this.hasScrollToBottomBefore:", this.hasScrollToBottomBefore); 630 | //console.log(event.scrollTop + ", maxScrollTop: " + maxScrollTop + ", hasExtend: " + this.extendBottom + ", hasBottom: " + this.hasScrollToBottomBefore); 631 | console.log(event.scrollTop + ", maxScrollTop: " + maxScrollTop + ", isMoveEnd: " + isMoveEnd); 632 | 633 | 634 | 635 | //---------------------------------------------------------------------------------------- 636 | 637 | //if we are at the bottom, animate the header/tabs back in 638 | /*if (event.scrollTop < maxScrollTop - 40) { 639 | 640 | this.hasReachBottomBefore = false; 641 | //this.content.setScrollElementStyle("padding-bottom", (this.content._pBottom) + "px"); 642 | this.extendBottom = false; 643 | console.log("--> reset padding-bottom"); 644 | 645 | } else if (event.scrollTop >= maxScrollTop) { 646 | 647 | if(this.hasReachBottomBefore) { 648 | y = 0; 649 | duration = this.defaultDuration; 650 | console.log("--> show"); 651 | } 652 | if(isMoveEnd) { 653 | this.hasReachBottomBefore = true; 654 | //this.content.setScrollElementStyle("padding-bottom", (this.content._pBottom + this.footerHeight) + "px"); 655 | this.extendBottom = true; 656 | console.log("--> extend padding-bottom"); 657 | } 658 | }*/ 659 | 660 | 661 | 662 | // ------------------------------ 663 | 664 | 665 | 666 | 667 | if (event.scrollTop >= maxScrollTop) { 668 | if(this.hasReachBottomBefore) { 669 | console.log("--> 1 show"); 670 | y = 0; 671 | duration = this.defaultDuration; 672 | } 673 | if(isMoveEnd) { 674 | console.log("--> 1 extend padding-bottom"); 675 | this.hasReachBottomBefore = true; 676 | this.extendBottom = true; 677 | } 678 | } 679 | 680 | /*if(event.scrollTop <= maxScrollTop) { 681 | if(this.hasBeyondMaxScrollTop) { 682 | console.log("--> 2 extend padding-bottom!"); 683 | this.extendBottom = true; 684 | } 685 | this.hasBeyondMaxScrollTop = false; 686 | 687 | } else { // > maxScrollTop 688 | console.log("--> 2 scroll to bottom!"); 689 | this.hasBeyondMaxScrollTop = true; 690 | 691 | if(this.extendBottom) { 692 | console.log("--> 2 show!"); 693 | y = 0; 694 | duration = this.defaultDuration; 695 | } 696 | }*/ 697 | 698 | // Reset bottom 699 | if (event.scrollTop < maxScrollTop - 40) { 700 | this.hasReachBottomBefore = false; 701 | this.extendBottom = false; 702 | console.log("--> reset padding-bottom"); 703 | } 704 | 705 | // Set bottom height 706 | if(this.extendBottom) { 707 | this.content.setScrollElementStyle("padding-bottom", (this.content._pBottom + this.footerHeight) + "px"); 708 | } else { 709 | this.content.setScrollElementStyle("padding-bottom", (this.content._pBottom) + "px"); 710 | } 711 | 712 | 713 | 714 | // ------------------------------ 715 | 716 | 717 | /* 718 | // ???? 719 | if(event.scrollTop >= maxScrollTop) { 720 | if(event.scrollTop < this.scrollTopPrev) { 721 | console.log("--> scroll to bottom!"); 722 | this.hasScrollToBottomBefore = true; 723 | this.content.setScrollElementStyle("padding-bottom", (this.content._pBottom + this.footerHeight) + "px"); 724 | 725 | } else if(this.hasScrollToBottomBefore) { 726 | console.log("--> show!"); 727 | y = 0; 728 | duration = this.defaultDuration; 729 | } 730 | } else if(event.scrollTop < maxScrollTop - 40){ 731 | console.log("--> show!"); 732 | this.hasScrollToBottomBefore = false; 733 | this.content.setScrollElementStyle("padding-bottom", (this.content._pBottom) + "px"); 734 | }*/ 735 | 736 | 737 | 738 | // ------------------------------ 739 | 740 | 741 | /* 742 | // ???? 743 | if(event.scrollTop >= maxScrollTop) { 744 | if(!this.hasExtendBottom) { 745 | if(event.scrollTop < this.scrollTopPrev) { 746 | console.log("--> scroll to bottom!"); 747 | this.hasScrollToBottomBefore = true; 748 | } 749 | } else { 750 | console.log("--> show!"); 751 | y = 0; 752 | duration = this.defaultDuration; 753 | } 754 | } else if(event.scrollTop < maxScrollTop) { 755 | if(this.hasScrollToBottomBefore) { 756 | this.hasScrollToBottomBefore = false; 757 | console.log("--> extend padding-bottom!"); 758 | this.hasExtendBottom = true; 759 | this.content.setScrollElementStyle("padding-bottom", (this.content._pBottom + this.footerHeight) + "px"); 760 | } else { 761 | console.log("reset padding-bottom"); 762 | this.hasExtendBottom = false; 763 | this.content.setScrollElementStyle("padding-bottom", (this.content._pBottom) + "px"); 764 | } 765 | }*/ 766 | 767 | 768 | 769 | // ------------------------------ 770 | 771 | 772 | 773 | // Work! 774 | /*if(event.scrollTop <= maxScrollTop) { 775 | if(this.hasBeyondMaxScrollTop) { 776 | console.log("--> extend padding-bottom!"); 777 | this.extendBottom = true; 778 | } 779 | this.hasBeyondMaxScrollTop = false; 780 | 781 | 782 | } else { // > maxScrollTop 783 | this.hasBeyondMaxScrollTop = true; 784 | console.log("--> scroll to bottom!"); 785 | 786 | if(this.extendBottom) { 787 | console.log("--> show!"); 788 | y = 0; 789 | duration = this.defaultDuration; 790 | } 791 | } 792 | 793 | if(event.scrollTop < maxScrollTop - 40) { 794 | this.extendBottom = false; 795 | } 796 | 797 | if(this.extendBottom) { 798 | this.content.setScrollElementStyle("padding-bottom", (this.content._pBottom + this.footerHeight) + "px"); 799 | } else { 800 | this.content.setScrollElementStyle("padding-bottom", (this.content._pBottom) + "px"); 801 | }*/ 802 | 803 | 804 | // ------------------------------ 805 | 806 | /* 807 | // Seem to be work 808 | if(event.scrollTop >= maxScrollTop) { 809 | if(scrollTopDiff <= 0) { 810 | console.log("--> Beyond MaxScrollTop! extend!"); 811 | this.hasBeyondMaxScrollTop = true; 812 | this.extendBottom = true; 813 | } else { // > 0 814 | if(this.hasBeyondMaxScrollTop) { 815 | console.log("--> show!"); 816 | y = 0; 817 | duration = this.defaultDuration; 818 | } 819 | } 820 | } else { 821 | if(scrollTopDiff <= 0) { 822 | console.log("--> reset beyondMaxScrollTop!"); 823 | this.hasBeyondMaxScrollTop = false; 824 | } 825 | } 826 | 827 | // Reset the bottom 828 | if(event.scrollTop < maxScrollTop - 40) { 829 | this.extendBottom = false; 830 | } 831 | 832 | // Set bottom height 833 | if(this.extendBottom) { 834 | this.content.setScrollElementStyle("padding-bottom", (this.content._pBottom + this.footerHeight) + "px"); 835 | } else { 836 | this.content.setScrollElementStyle("padding-bottom", (this.content._pBottom) + "px"); 837 | }*/ 838 | 839 | // ------------------------------ 840 | 841 | /*if(this.hasStopMoving) { 842 | if(!this.hasClearStopMoving) { 843 | console.log("---> clear stop moving!"); 844 | this.hasClearStopMoving = true; 845 | this.content.setScrollElementStyle("overflow-y", ""); 846 | } 847 | 848 | if(scrollTopDiff < 0) { // scroll up 849 | this.hasStopMoving = false; 850 | 851 | } else if(scrollTopDiff > 0) { // scroll down 852 | this.hasStopMoving = false; 853 | console.log("--> show!"); 854 | y = 0; 855 | duration = this.defaultDuration; 856 | } 857 | } 858 | 859 | if(event.scrollTop >= maxScrollTop) { 860 | if(!this.hasStopMoving) { 861 | console.log("---> stop moving!"); 862 | this.hasStopMoving = true; 863 | this.hasClearStopMoving = false; 864 | 865 | this.extendBottom = true; 866 | this.content.setScrollElementStyle("overflow-y", "hidden"); 867 | //this.content.scrollTo(0, maxScrollTop); 868 | } 869 | } 870 | 871 | 872 | // Reset the bottom 873 | if(event.scrollTop < maxScrollTop - 40) { 874 | this.extendBottom = false; 875 | } 876 | 877 | // Set bottom height 878 | if(this.extendBottom) { 879 | this.content.setScrollElementStyle("padding-bottom", (this.content._pBottom + this.footerHeight) + "px"); 880 | } else { 881 | this.content.setScrollElementStyle("padding-bottom", (this.content._pBottom) + "px"); 882 | }*/ 883 | 884 | 885 | 886 | 887 | 888 | //---------------------------------------------------------------------------------------- 889 | 890 | /*console.log({ 891 | y: y, 892 | endY: this.endY, 893 | 894 | scrollTop: event.scrollTop, 895 | maxScrollTop: maxScrollTop, 896 | footerHeight: this.footerHeight, 897 | });*/ 898 | 899 | 900 | 901 | 902 | //if previous and current y are the same, no need to continue 903 | if (this.yPrev !== y) { 904 | //this.modifyDom(y, duration, event.domWrite); 905 | 906 | let yDiff = y - this.yPrev; 907 | if(-30 < yDiff && yDiff < 30) { 908 | this.modifyDom(y, duration, event.domWrite); 909 | } else { 910 | this.modifyDom(y, 50, event.domWrite); 911 | } 912 | } 913 | 914 | //console.log({ y: y, scrollTop: event.scrollTop, maxScrollTop: maxScrollTop}); 915 | 916 | 917 | this.yPrev = y; 918 | this.scrollTopPrev = event.scrollTop; 919 | } 920 | 921 | 922 | 923 | 924 | 925 | private modifyDom(y: number, duration: number, dowWrite: DomWrite) { 926 | dowWrite(() => { 927 | y = constrain(y, 0, this.endY); 928 | 929 | 930 | //console.group(); 931 | 932 | 933 | // Header Items 934 | this.headerItems.forEach((item) => { 935 | 936 | let dy_transit = constrain((y - item.transition.start), 0, item.transition.interval); 937 | let dy_move = constrain((y - item.transition.end), 0, Infinity); 938 | 939 | //console.log(y, "dy_transit:", dy_transit, "dy_move:", dy_move, TransitionType[item.transition.type]); 940 | 941 | if(item.type === ItemType.Tabbar) { 942 | 943 | let val = constrain((dy_transit / item.height), 0, 1); 944 | $(item.ele).css({ 945 | height: item.height - dy_transit, 946 | }) 947 | this.translateElementY(item.ele, -dy_move, duration); 948 | 949 | } else { 950 | 951 | if(item.transition.type === TransitionType.Shrink) { 952 | // Shrinking 953 | 954 | let val = constrain((dy_transit / item.height), 0, 1); 955 | $(item.ele).css({ 956 | height: item.height - dy_transit, 957 | marginBottom: 0, // marginBottom: dy_transit, 958 | }) 959 | this.translateElementY(item.ele, 0, duration); 960 | 961 | 962 | /*$(item.ele).children().not(".toolbar-background") 963 | .each((index, navbarChild) => { 964 | $(navbarChild).css("transform", `scale(${1 - val})`); 965 | });*/ 966 | 967 | $(item.ele).children().not(".toolbar-background, .toolbar-content") 968 | .each((index, navbarChild) => { 969 | $(navbarChild).css("transform", `scale(${1 - val})`); 970 | }); 971 | $(item.ele).children(".toolbar-content").children() 972 | .each((index, navbarChild) => { 973 | $(navbarChild).css("transform", `scale(${1 - val})`); 974 | }); 975 | 976 | } else { 977 | // Translating 978 | 979 | let val = constrain((dy_transit / item.height), 0, 1); 980 | 981 | $(item.ele).css({ 982 | height: item.height, 983 | marginBottom: -dy_transit, // marginBottom: 0, 984 | }) 985 | this.translateElementY(item.ele, -dy_transit, duration); 986 | 987 | 988 | $(item.ele).children().not(".toolbar-background") 989 | .each((index, navbarChild) => { 990 | $(navbarChild).css("opacity", 1 - val); 991 | }); 992 | } 993 | 994 | 995 | } 996 | }); 997 | //console.groupEnd(); 998 | 999 | 1000 | // Footer Items 1001 | this.footerItems.forEach((item) => { 1002 | this.translateElementY(item.ele, y, duration); 1003 | }); 1004 | 1005 | 1006 | // Content 1007 | /*if(y <= this.endY) { 1008 | this.content.setScrollElementStyle("margin-top", (this.headerHeight - y) + "px"); 1009 | } else { 1010 | this.content.setScrollElementStyle("margin-top", (this.headerHeight - this.endY) + "px"); 1011 | } 1012 | 1013 | if(y <= this.footerHeight) { 1014 | this.content.setScrollElementStyle("margin-bottom", (this.footerHeight - y) + "px"); 1015 | }*/ 1016 | }); 1017 | } 1018 | 1019 | private translateElementY(ele: HTMLElement, y: number, duration: number) { 1020 | this.renderer.setElementStyle(ele, "webkitTransform", `translate3d(0, ${y}px,0)`); 1021 | 1022 | if (duration && !ele.style.transitionDuration) { 1023 | ele.style.transitionDuration = duration + "ms"; 1024 | setTimeout(() => { 1025 | ele.style.transitionDuration = ""; 1026 | }, this.defaultDelay); 1027 | } 1028 | } 1029 | } 1030 | -------------------------------------------------------------------------------- /src/components/scroll-hide/scroll-hide.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Directive, 3 | ElementRef, NgZone, HostListener, Input, Renderer, 4 | OnInit, OnDestroy, 5 | } from '@angular/core'; 6 | 7 | import { Content, ScrollEvent, NavController, ViewController } from 'ionic-angular' 8 | import { Subscription } from 'rxjs'; 9 | 10 | import * as $ from 'jquery' 11 | 12 | 13 | /* 14 | 15 | 16 | ion-header { 17 | pointer-events: none; 18 | } 19 | ion-header > * { 20 | pointer-events: all; 21 | } 22 | 23 | 24 | 25 | 26 | ion-header > ion-navbar { 27 | position: relative; 28 | } 29 | 30 | 31 | 32 | ion-tabs > .tabbar { 33 | overflow: hidden; 34 | } 35 | ion-tabs > .tabbar > *{ 36 | align-self: flex-end; 37 | } 38 | 39 | */ 40 | 41 | 42 | type DomWrite = (fn: (timeStamp?: number) => void, ctx?: any) => void; 43 | 44 | 45 | function constrain(val: number, min: number, max: number) { 46 | return Math.min(max, Math.max(val, min)); 47 | } 48 | 49 | 50 | 51 | 52 | 53 | enum ItemType { Navbar, Toolbar, Tabbar } 54 | 55 | enum TransitionType { Static, Translate, Shrink } 56 | interface Transition { 57 | type: TransitionType; 58 | start: number; 59 | end: number; 60 | interval: number; 61 | } 62 | 63 | /* 64 | * startY EndY 65 | * shrinkStart TransitionStart MoveStart 66 | * | ------------- | ---------------- | ----------> 67 | * Shrink Translate 68 | * 69 | * 70 | * transitionStart transitionEnd 71 | * | ------------- | ----------> 72 | * Shrink 73 | * 74 | * transitionStart transitionEnd 75 | * | ------------- | ----------> 76 | * Translate 77 | * 78 | */ 79 | class Item { 80 | public type: ItemType; 81 | public height: number; 82 | 83 | public transition: Transition = { type: 0, start: 0, end: 0, interval: 0 }; 84 | public scrollShrinkVal: number = 0; 85 | 86 | 87 | constructor(public ele: HTMLElement) { 88 | 89 | if(ele.classList.contains("tabbar")) { 90 | this.type = ItemType.Tabbar; 91 | } else if(ele.tagName === "ION-NAVBAR") { 92 | this.type = ItemType.Navbar; 93 | } else { 94 | this.type = ItemType.Toolbar; 95 | } 96 | 97 | this.height = ele.offsetHeight; 98 | Subscription 99 | 100 | 101 | let shrinkable = ele.hasAttribute("scroll-hide-shrink"); 102 | let translatable = ele.hasAttribute("scroll-hide-translate"); 103 | 104 | if(shrinkable) { 105 | this.transition.type = TransitionType.Shrink; 106 | var scrollShrinkVal = parseFloat(ele.getAttribute("scroll-hide-shrink")); 107 | if(isNaN(scrollShrinkVal)) scrollShrinkVal = 1; 108 | this.scrollShrinkVal = scrollShrinkVal; 109 | 110 | } else if(translatable) { 111 | this.transition.type = TransitionType.Translate; 112 | 113 | } else { 114 | this.transition.type = TransitionType.Static; 115 | 116 | } 117 | 118 | 119 | if(this.type === ItemType.Tabbar) { 120 | this.transition.type = TransitionType.Shrink; 121 | this.scrollShrinkVal = 0.5; 122 | 123 | //this.transition.type = TransitionType.Translate; 124 | 125 | //this.transition.type = TransitionType.Static; 126 | } 127 | } 128 | 129 | } 130 | 131 | 132 | @Directive({ 133 | selector: '[scroll-hide]' // Attribute selector 134 | }) 135 | export class ScrollHide implements OnInit, OnDestroy { 136 | 137 | @Input("scroll-hide") viewCtrl: ViewController; 138 | 139 | 140 | headerItems: Item[] = []; 141 | footerItems: Item[] = []; 142 | 143 | headerEle: HTMLElement; 144 | footerEle: HTMLElement; 145 | 146 | //--------------------------------- 147 | 148 | defaultDelay: number = 400 * 2; 149 | defaultDuration: number = 400; 150 | 151 | 152 | headerHeight: number; 153 | footerHeight: number; 154 | endY: number; 155 | 156 | defaultEnd: number = 0; 157 | y: number = 0; 158 | yPrev: number = 0; 159 | scrollTopPrev: number = 0; 160 | 161 | 162 | initiated: boolean = false; 163 | viewEntered: boolean = false; 164 | 165 | //--------------------------------- 166 | 167 | subscriptions: Subscription[] = []; 168 | 169 | //--------------------------------- 170 | 171 | constructor(private content: Content, 172 | private renderer: Renderer, 173 | private zone: NgZone) {} 174 | 175 | 176 | 177 | ngOnInit() { 178 | this.content.fullscreen = true; 179 | 180 | 181 | // ViewCtrl LifeCycle 182 | this.subscriptions.push( 183 | this.viewCtrl.didEnter.subscribe(() => { 184 | this.init(); 185 | }) 186 | ); 187 | this.subscriptions.push( 188 | this.viewCtrl.willLeave.subscribe(() => { 189 | this.resetStyle(); 190 | }) 191 | ); 192 | 193 | 194 | 195 | // Content Scroll Event 196 | var prevEndTimestep: number = 0; 197 | this.subscriptions.push( 198 | this.content.ionScroll.subscribe(event => { 199 | if(event.timeStamp > prevEndTimestep + 200) { // prevent multiple triggered of scroll event 200 | this.onContentScroll(event, false); 201 | } 202 | }) 203 | ); 204 | this.subscriptions.push( 205 | this.content.ionScrollEnd.subscribe(event => { 206 | if(event.timeStamp > prevEndTimestep + 200) { 207 | this.onContentScroll(event, true); 208 | } 209 | prevEndTimestep = event.timeStamp; 210 | }) 211 | ); 212 | } 213 | 214 | ngOnDestroy() { 215 | console.log("ngOnDestroy()"); 216 | 217 | this.resetStyle(); 218 | 219 | 220 | this.viewCtrl = null; 221 | 222 | this.headerItems = null; 223 | this.footerItems = null; 224 | 225 | this.headerEle = null; 226 | this.footerEle = null; 227 | 228 | this.subscriptions.forEach(sub => sub.unsubscribe()); 229 | this.subscriptions = null; 230 | } 231 | 232 | 233 | 234 | private init() { 235 | this.y = 0; 236 | this.yPrev = 0; 237 | this.scrollTopPrev = this.content.scrollTop; 238 | 239 | 240 | 241 | this.content.resize(); 242 | 243 | 244 | let contentEle = this.content.getNativeElement(); 245 | let headerEle = contentEle.parentElement.querySelector("ion-header"); 246 | let footerEle = contentEle.parentElement.querySelector("ion-footer"); 247 | 248 | if(headerEle) { 249 | this.headerItems = $(headerEle) 250 | .children() 251 | .toArray().map(ele => new Item(ele)); 252 | } else { 253 | this.headerItems = []; 254 | } 255 | 256 | if(footerEle) { 257 | this.footerItems = $(footerEle) 258 | .children() 259 | .toArray().map(ele => new Item(ele)); 260 | } else { 261 | this.footerItems = []; 262 | } 263 | 264 | 265 | 266 | 267 | let hasTabbar = (this.content._tabs !== null); 268 | if(hasTabbar) { 269 | //this.tabbarEle = this.content._tabs._tabbar.nativeElement; 270 | let tabbarEle = ( this.content._tabs)._tabbar.nativeElement; 271 | 272 | if(this.content._tabsPlacement === "bottom") { 273 | this.footerItems.push(new Item(tabbarEle)); 274 | } else { 275 | this.headerItems.push(new Item(tabbarEle)); 276 | } 277 | } 278 | 279 | 280 | this.headerItems = this.headerItems.reverse(); 281 | this.footerItems = this.footerItems.reverse(); 282 | 283 | 284 | 285 | 286 | var headerHeight = 0, 287 | footerHeight = 0; 288 | 289 | //console.group("headerItems") 290 | 291 | var shrinkableHeightSum = 0; 292 | this.headerItems.forEach((item, index) => { 293 | headerHeight += item.ele.offsetHeight; 294 | 295 | // Navbar would not be raised without setting position to relative 296 | if(item.type === ItemType.Navbar) { 297 | $(item.ele).css("position", "relative") 298 | } 299 | 300 | 301 | // Prevent overlapping of bottom element 302 | if(item.type === ItemType.Tabbar) { 303 | $(item.ele).css("overflow", "hidden"); 304 | $(item.ele).children().css("align-self", "flex-end"); 305 | } else { 306 | $(item.ele).css("z-index", index + 1); 307 | } 308 | 309 | 310 | // For shrinking 311 | //$(item.ele).css("padding-top", "0"); 312 | //$(item.ele).css("padding-bottom", "0"); 313 | 314 | 315 | $(item.ele).css("min-height", "0"); 316 | $(item.ele).css("height", item.height + "px"); 317 | 318 | 319 | switch(item.transition.type) { 320 | 321 | case TransitionType.Shrink: 322 | 323 | let shrinkHeight = (item.height * item.scrollShrinkVal); 324 | item.transition.start = (shrinkableHeightSum); 325 | item.transition.end = (shrinkableHeightSum + shrinkHeight); 326 | item.transition.interval = (shrinkHeight); 327 | shrinkableHeightSum += (shrinkHeight); 328 | break; 329 | 330 | case TransitionType.Translate: 331 | 332 | item.transition.start = (shrinkableHeightSum); 333 | item.transition.end = (shrinkableHeightSum + item.height); 334 | item.transition.interval = (item.height); 335 | shrinkableHeightSum += (item.height); 336 | break; 337 | 338 | case TransitionType.Static: 339 | 340 | item.transition.start = (shrinkableHeightSum); 341 | item.transition.end = (shrinkableHeightSum); 342 | item.transition.interval = (0); 343 | break; 344 | } 345 | 346 | //console.log(item, item.ele.offsetHeight); 347 | }); 348 | this.headerHeight = headerHeight; 349 | this.defaultEnd = this.headerHeight * 2; 350 | this.endY = shrinkableHeightSum; 351 | 352 | //console.groupEnd(); 353 | 354 | 355 | //console.group("footerItems") 356 | this.footerItems.forEach(item => { 357 | footerHeight += item.ele.offsetHeight; 358 | //console.log(item.ele, item.ele.offsetHeight); 359 | }); 360 | this.footerHeight = footerHeight; 361 | //console.groupEnd(); 362 | 363 | //console.log("headerHeight:", headerHeight, ", footerHeight:", footerHeight, ", endY:", this.endY); 364 | 365 | 366 | /* 367 | ion-header { 368 | pointer-events: none; 369 | } 370 | 371 | ion-header > * { 372 | pointer-events: all; 373 | }*/ 374 | 375 | if(headerEle) { 376 | $(headerEle).css("pointer-events", "none"); 377 | $(headerEle).children().css("pointer-events", "all"); 378 | } 379 | if(footerEle) { 380 | $(footerEle).css("pointer-events", "none"); 381 | $(footerEle).children().css("pointer-events", "all"); 382 | } 383 | 384 | this.headerEle = headerEle; 385 | this.footerEle = footerEle; 386 | 387 | 388 | 389 | } 390 | 391 | 392 | private resetStyle() { 393 | this.headerItems.forEach((item, index) => { 394 | 395 | if(item.type === ItemType.Navbar) { 396 | $(item.ele).css("position", ""); 397 | } 398 | 399 | if(item.type === ItemType.Tabbar) { 400 | $(item.ele).css("overflow", ""); 401 | $(item.ele).children().css("align-self", "center"); 402 | } else { 403 | $(item.ele).css("z-index", ""); 404 | } 405 | 406 | }); 407 | 408 | if(this.headerEle) { 409 | $(this.headerEle).css("pointer-events", "none"); 410 | $(this.headerEle).children().css("pointer-events", "all"); 411 | } 412 | if(this.footerEle) { 413 | $(this.footerEle).css("pointer-events", "none"); 414 | $(this.footerEle).children().css("pointer-events", "all"); 415 | } 416 | 417 | 418 | // Header Items 419 | this.headerItems.forEach((item) => { 420 | 421 | $(item.ele).css("height", ""); 422 | $(item.ele).css("min-height", ""); 423 | $(item.ele).css("webkitTransform", ""); 424 | 425 | 426 | if(item.type !== ItemType.Tabbar) { 427 | 428 | $(item.ele).css("marginBottom", ""); 429 | 430 | if(item.transition.type === TransitionType.Shrink) { 431 | // Shrinking 432 | $(item.ele).children().not(".toolbar-background, .toolbar-content") 433 | .each((index, navbarChild) => { 434 | $(navbarChild).css("transform", ""); 435 | }); 436 | $(item.ele).children(".toolbar-content").children() 437 | .each((index, navbarChild) => { 438 | $(navbarChild).css("transform", ""); 439 | }); 440 | 441 | } else { 442 | // Translating 443 | $(item.ele).children().not(".toolbar-background") 444 | .each((index, navbarChild) => { 445 | $(navbarChild).css("opacity", ""); 446 | }); 447 | } 448 | 449 | } 450 | }); 451 | 452 | 453 | // Footer Items 454 | this.footerItems.forEach((item) => { 455 | $(item.ele).css("webkitTransform", ""); 456 | }); 457 | 458 | 459 | // Content 460 | this.content.setScrollElementStyle("padding-bottom", (this.content._pBottom) + "px"); 461 | } 462 | 463 | 464 | /*cachedTabbarStayle: { 465 | top: string; 466 | height?: string; 467 | webkitTransform: string; 468 | }; 469 | private storeTabbarStyle() { 470 | console.log("storeTabbarStyle()"); 471 | 472 | this.headerItems.forEach((item) => { 473 | if(item.type === ItemType.Tabbar) { 474 | this.cachedTabbarStayle = { 475 | top: $(item.ele).css("top"), 476 | height: $(item.ele).css("height"), 477 | webkitTransform: $(item.ele).css("webkitTransform") 478 | }; 479 | } 480 | }); 481 | this.footerItems.forEach((item) => { 482 | if(item.type === ItemType.Tabbar) { 483 | this.cachedTabbarStayle = { 484 | top: $(item.ele).css("top"), 485 | webkitTransform: $(item.ele).css("webkitTransform") 486 | }; 487 | } 488 | }); 489 | console.log("this.cachedTabbarStayle:", this.cachedTabbarStayle); 490 | } 491 | private resetTabbarStyle() { 492 | console.log("resetTabbarStyle()"); 493 | 494 | this.headerItems.forEach((item) => { 495 | if(item.type === ItemType.Tabbar) { 496 | $(item.ele).css("overflow", ""); 497 | $(item.ele).children().css("align-self", "center"); 498 | 499 | $(item.ele).css("height", ""); 500 | $(item.ele).css("min-height", ""); 501 | $(item.ele).css("webkitTransform", ""); 502 | } 503 | }); 504 | this.footerItems.forEach((item) => { 505 | if(item.type === ItemType.Tabbar) { 506 | $(item.ele).css("webkitTransform", ""); 507 | } 508 | }); 509 | } 510 | private appplyStoredTabbarStyle() { 511 | console.log("appplyStoredTabbarStyle()"); 512 | 513 | this.headerItems.forEach((item) => { 514 | if(item.type === ItemType.Tabbar) { 515 | $(item.ele).css("overflow", "hidden"); 516 | $(item.ele).children().css("align-self", "flex-end"); 517 | $(item.ele).css("min-height", "0"); 518 | 519 | $(item.ele).css("top", this.cachedTabbarStayle.top); 520 | $(item.ele).css("height", this.cachedTabbarStayle.height); 521 | $(item.ele).css("webkitTransform", this.cachedTabbarStayle.webkitTransform); 522 | } 523 | }); 524 | this.footerItems.forEach((item) => { 525 | if(item.type === ItemType.Tabbar) { 526 | $(item.ele).css("top", this.cachedTabbarStayle.top); 527 | $(item.ele).css("webkitTransform", this.cachedTabbarStayle.webkitTransform); 528 | } 529 | }); 530 | }*/ 531 | 532 | 533 | 534 | hasStopMoving = false; 535 | hasClearStopMoving = false; 536 | 537 | extendBottom = false; 538 | hasReachBottomBefore = false; 539 | hasBeyondMaxScrollTop = false; 540 | 541 | 542 | onContentScroll(event: ScrollEvent, isMoveEnd: boolean) { 543 | 544 | // For Ionic 3 545 | let maxScrollTop = this.content._scrollContent.nativeElement.scrollHeight - (event.contentTop + event.contentHeight + event.contentBottom) - (this.extendBottom ? this.footerHeight : 0); 546 | // For Ionic 2 547 | //let maxScrollTop = this.content._scrollEle.scrollHeight - (event.contentTop + event.contentHeight + event.contentBottom) - (this.extendBottom ? this.footerHeight : 0); // For ionic2 548 | 549 | 550 | var duration = 0; 551 | let scrollTopDiff = event.scrollTop - this.scrollTopPrev; 552 | 553 | let y = (event.scrollTop >= 0) ? constrain(this.yPrev + scrollTopDiff, 0, this.defaultEnd) : 0; 554 | 555 | 556 | //console.log(event.scrollTop + ", maxScrollTop: " + maxScrollTop + ", isMoveEnd: " + isMoveEnd); 557 | 558 | 559 | 560 | //---------------------------------------------------------------------------------------- 561 | 562 | if (event.scrollTop >= maxScrollTop) { 563 | if(this.hasReachBottomBefore) { 564 | //console.log("--> 1 show"); 565 | y = 0; 566 | duration = this.defaultDuration; 567 | } 568 | if(isMoveEnd) { 569 | //console.log("--> 1 extend padding-bottom"); 570 | this.hasReachBottomBefore = true; 571 | this.extendBottom = true; 572 | } 573 | } 574 | 575 | 576 | // Reset bottom 577 | if (event.scrollTop < maxScrollTop - 40) { 578 | this.hasReachBottomBefore = false; 579 | this.extendBottom = false; 580 | //console.log("--> reset padding-bottom"); 581 | } 582 | 583 | // Set bottom height 584 | if(this.extendBottom) { 585 | this.content.setScrollElementStyle("padding-bottom", (this.content._pBottom + this.footerHeight) + "px"); 586 | } else { 587 | this.content.setScrollElementStyle("padding-bottom", (this.content._pBottom) + "px"); 588 | } 589 | 590 | 591 | 592 | //---------------------------------------------------------------------------------------- 593 | 594 | 595 | //if previous and current y are the same, no need to continue 596 | if (this.yPrev !== y) { 597 | //this.modifyDom(y, duration, event.domWrite); 598 | 599 | let yDiff = y - this.yPrev; 600 | if(-30 < yDiff && yDiff < 30) { 601 | this.modifyDom(y, duration, event.domWrite); 602 | } else { 603 | this.modifyDom(y, 50, event.domWrite); 604 | } 605 | } 606 | 607 | //console.log({ y: y, scrollTop: event.scrollTop, maxScrollTop: maxScrollTop}); 608 | 609 | 610 | this.yPrev = y; 611 | this.scrollTopPrev = event.scrollTop; 612 | } 613 | 614 | 615 | 616 | 617 | 618 | private modifyDom(y: number, duration: number, dowWrite: DomWrite) { 619 | dowWrite(() => { 620 | y = constrain(y, 0, this.endY); 621 | 622 | 623 | 624 | // Header Items 625 | this.headerItems.forEach((item) => { 626 | 627 | let dy_transit = constrain((y - item.transition.start), 0, item.transition.interval); 628 | let dy_move = constrain((y - item.transition.end), 0, Infinity); 629 | 630 | //console.log(y, "dy_transit:", dy_transit, "dy_move:", dy_move, TransitionType[item.transition.type]); 631 | 632 | if(item.type === ItemType.Tabbar) { 633 | 634 | let val = constrain((dy_transit / item.height), 0, 1); 635 | $(item.ele).css({ 636 | height: item.height - dy_transit, 637 | }) 638 | this.translateElementY(item.ele, -dy_move, duration); 639 | 640 | } else { 641 | 642 | if(item.transition.type === TransitionType.Shrink) { 643 | // Shrinking 644 | 645 | let val = constrain((dy_transit / item.height), 0, 1); 646 | $(item.ele).css({ 647 | height: item.height - dy_transit, 648 | marginBottom: 0, // marginBottom: dy_transit, 649 | }) 650 | this.translateElementY(item.ele, 0, duration); 651 | 652 | 653 | $(item.ele).children().not(".toolbar-background, .toolbar-content") 654 | .each((index, navbarChild) => { 655 | $(navbarChild).css("transform", `scale(${1 - val})`); 656 | }); 657 | $(item.ele).children(".toolbar-content").children() 658 | .each((index, navbarChild) => { 659 | $(navbarChild).css("transform", `scale(${1 - val})`); 660 | }); 661 | 662 | } else { 663 | // Translating 664 | 665 | let val = constrain((dy_transit / item.height), 0, 1); 666 | 667 | $(item.ele).css({ 668 | height: item.height, 669 | marginBottom: -dy_transit, // marginBottom: 0, 670 | }) 671 | this.translateElementY(item.ele, -dy_transit, duration); 672 | 673 | 674 | $(item.ele).children().not(".toolbar-background") 675 | .each((index, navbarChild) => { 676 | $(navbarChild).css("opacity", 1 - val); 677 | }); 678 | } 679 | 680 | 681 | } 682 | }); 683 | 684 | 685 | // Footer Items 686 | this.footerItems.forEach((item) => { 687 | this.translateElementY(item.ele, y, duration); 688 | }); 689 | }); 690 | } 691 | 692 | private translateElementY(ele: HTMLElement, y: number, duration: number) { 693 | this.renderer.setElementStyle(ele, "webkitTransform", `translate3d(0, ${y}px,0)`); 694 | 695 | if (duration && !ele.style.transitionDuration) { 696 | ele.style.transitionDuration = duration + "ms"; 697 | setTimeout(() => { 698 | ele.style.transitionDuration = ""; 699 | }, this.defaultDelay); 700 | } 701 | } 702 | } 703 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ionic App 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ionic", 3 | "short_name": "Ionic", 4 | "start_url": "index.html", 5 | "display": "standalone", 6 | "icons": [{ 7 | "src": "assets/imgs/logo.png", 8 | "sizes": "512x512", 9 | "type": "image/png" 10 | }], 11 | "background_color": "#4e8ef7", 12 | "theme_color": "#4e8ef7" 13 | } -------------------------------------------------------------------------------- /src/pages/about/about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | About 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 19 | 20 | 21 | Solid 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | Paid 32 | Free 33 | Top 34 | 35 | 36 | 37 | 38 | 39 | 40 |

Welcome to Ionic!

41 |

42 | This starter project comes with simple tabs-based layout for apps that are going to primarily use a Tabbed UI. 43 |

44 |

45 | Take a look at the src/pages/ directory to add or change tabs, update any existing page or create new pages. 46 |

47 | 48 | 49 |

Welcome to Ionic!

50 |

51 | This starter project comes with simple tabs-based layout for apps that are going to primarily use a Tabbed UI. 52 |

53 |

54 | Take a look at the src/pages/ directory to add or change tabs, update any existing page or create new pages. 55 |

56 | 57 | 58 |

Welcome to Ionic!

59 |

60 | This starter project comes with simple tabs-based layout for apps that are going to primarily use a Tabbed UI. 61 |

62 |

63 | Take a look at the src/pages/ directory to add or change tabs, update any existing page or create new pages. 64 |

65 | 66 | 67 |

Don’t Miss These Navigation Bar Interactions in iOS8

68 |

69 | Have you noticed how nicely the mobile Safari navigation bar condenses on scroll, and how the tab bar disappears? 70 | 71 | Video Player 72 | 00:1400:19 73 | In iOS8, Apple has made this type of interaction (and more!) very easily available to us all – well, almost… While Apple demoed the condensing navigation bar at WWDC, they have since changed it to hiding the navigation instead, and the tab bar is not included (I’m guessing they’ll add separate tab bar hiding properties later on…). 74 | 75 | Here are the cool new navigation bar interactions in iOS8 that will allow the user to see more of your content: 76 |

77 |
78 | 79 | -------------------------------------------------------------------------------- /src/pages/about/about.scss: -------------------------------------------------------------------------------- 1 | page-about { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/pages/about/about.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { NavController, ViewController } from 'ionic-angular'; 3 | 4 | @Component({ 5 | selector: 'page-about', 6 | templateUrl: 'about.html' 7 | }) 8 | export class AboutPage { 9 | 10 | constructor(public navCtrl: NavController, 11 | public viewCtrl: ViewController) { 12 | 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/pages/contact/contact.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Contact 5 | 6 | 7 | 8 | 9 | 10 | 11 | Follow us on Twitter 12 | 13 | 14 | @ionicframework 15 | 16 | 17 | 18 |

Welcome to Ionic!

19 |

20 | This starter project comes with simple tabs-based layout for apps that are going to primarily use a Tabbed UI. 21 |

22 |

23 | Take a look at the src/pages/ directory to add or change tabs, update any existing page or create new pages. 24 |

25 | 26 | 27 |

Don’t Miss These Navigation Bar Interactions in iOS8

28 |

29 | Have you noticed how nicely the mobile Safari navigation bar condenses on scroll, and how the tab bar disappears? 30 | 31 | Video Player 32 | 00:1400:19 33 | In iOS8, Apple has made this type of interaction (and more!) very easily available to us all – well, almost… While Apple demoed the condensing navigation bar at WWDC, they have since changed it to hiding the navigation instead, and the tab bar is not included (I’m guessing they’ll add separate tab bar hiding properties later on…). 34 | 35 | Here are the cool new navigation bar interactions in iOS8 that will allow the user to see more of your content: 36 |

37 |

Welcome to Ionic!

38 |

39 | This starter project comes with simple tabs-based layout for apps that are going to primarily use a Tabbed UI. 40 |

41 |

42 | Take a look at the src/pages/ directory to add or change tabs, update any existing page or create new pages. 43 |

44 | 45 | 46 |

Don’t Miss These Navigation Bar Interactions in iOS8

47 |

48 | Have you noticed how nicely the mobile Safari navigation bar condenses on scroll, and how the tab bar disappears? 49 | 50 | Video Player 51 | 00:1400:19 52 | In iOS8, Apple has made this type of interaction (and more!) very easily available to us all – well, almost… While Apple demoed the condensing navigation bar at WWDC, they have since changed it to hiding the navigation instead, and the tab bar is not included (I’m guessing they’ll add separate tab bar hiding properties later on…). 53 | 54 | Here are the cool new navigation bar interactions in iOS8 that will allow the user to see more of your content: 55 |

56 |

Welcome to Ionic!

57 |

58 | This starter project comes with simple tabs-based layout for apps that are going to primarily use a Tabbed UI. 59 |

60 |

61 | Take a look at the src/pages/ directory to add or change tabs, update any existing page or create new pages. 62 |

63 | 64 | 65 |

Don’t Miss These Navigation Bar Interactions in iOS8

66 |

67 | Have you noticed how nicely the mobile Safari navigation bar condenses on scroll, and how the tab bar disappears? 68 | 69 | Video Player 70 | 00:1400:19 71 | In iOS8, Apple has made this type of interaction (and more!) very easily available to us all – well, almost… While Apple demoed the condensing navigation bar at WWDC, they have since changed it to hiding the navigation instead, and the tab bar is not included (I’m guessing they’ll add separate tab bar hiding properties later on…). 72 | 73 | Here are the cool new navigation bar interactions in iOS8 that will allow the user to see more of your content: 74 |

75 |
76 | -------------------------------------------------------------------------------- /src/pages/contact/contact.scss: -------------------------------------------------------------------------------- 1 | page-contact { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/pages/contact/contact.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { NavController } from 'ionic-angular'; 3 | 4 | import { HomePage } from './../home/home'; 5 | 6 | 7 | @Component({ 8 | selector: 'page-contact', 9 | templateUrl: 'contact.html' 10 | }) 11 | export class ContactPage { 12 | 13 | homePage = HomePage; 14 | 15 | constructor(public navCtrl: NavController) { 16 | 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/pages/home/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Home 5 | 6 | 7 | 8 | 9 | 10 | 11 | 47 | 48 | 49 | 50 | 80 | 81 | 82 | 83 | 84 | 85 |

Welcome to Ionic!

86 |

87 | This starter project comes with simple tabs-based layout for apps that are going to primarily use a Tabbed UI. 88 |

89 |

90 | Take a look at the src/pages/ directory to add or change tabs, update any existing page or create new pages. 91 |

92 | 93 | 94 | 95 | 96 |

Welcome to Ionic!

97 |

98 | This starter project comes with simple tabs-based layout for apps that are going to primarily use a Tabbed UI. 99 |

100 |

101 | Take a look at the src/pages/ directory to add or change tabs, update any existing page or create new pages. 102 |

103 | 104 | 105 |

Welcome to Ionic!

106 |

107 | This starter project comes with simple tabs-based layout for apps that are going to primarily use a Tabbed UI. 108 |

109 |

110 | Take a look at the src/pages/ directory to add or change tabs, update any existing page or create new pages. 111 |

112 | 113 | 114 |

Don’t Miss These Navigation Bar Interactions in iOS8

115 |

116 | Have you noticed how nicely the mobile Safari navigation bar condenses on scroll, and how the tab bar disappears? 117 | 118 | Video Player 119 | 00:1400:19 120 | In iOS8, Apple has made this type of interaction (and more!) very easily available to us all – well, almost… While Apple demoed the condensing navigation bar at WWDC, they have since changed it to hiding the navigation instead, and the tab bar is not included (I’m guessing they’ll add separate tab bar hiding properties later on…). 121 | 122 | Here are the cool new navigation bar interactions in iOS8 that will allow the user to see more of your content: 123 |

124 |
125 | 126 | 127 | -------------------------------------------------------------------------------- /src/pages/home/home.scss: -------------------------------------------------------------------------------- 1 | page-home { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/pages/home/home.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { NavController, ViewController } from 'ionic-angular'; 3 | 4 | 5 | import { ContactPage } from './../contact/contact'; 6 | 7 | 8 | 9 | @Component({ 10 | selector: 'page-home', 11 | templateUrl: 'home.html' 12 | }) 13 | export class HomePage { 14 | 15 | page = ContactPage; 16 | 17 | 18 | constructor(public navCtrl: NavController, 19 | public viewCtrl: ViewController) { 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/pages/tabs/tabs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/pages/tabs/tabs.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { AboutPage } from '../about/about'; 4 | import { ContactPage } from '../contact/contact'; 5 | import { HomePage } from '../home/home'; 6 | 7 | @Component({ 8 | templateUrl: 'tabs.html' 9 | }) 10 | export class TabsPage { 11 | 12 | tab1Root = HomePage; 13 | tab2Root = AboutPage; 14 | tab3Root = ContactPage; 15 | 16 | constructor() { 17 | 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/service-worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Check out https://googlechrome.github.io/sw-toolbox/ for 3 | * more info on how to use sw-toolbox to custom configure your service worker. 4 | */ 5 | 6 | 7 | 'use strict'; 8 | importScripts('./build/sw-toolbox.js'); 9 | 10 | self.toolbox.options.cache = { 11 | name: 'ionic-cache' 12 | }; 13 | 14 | // pre-cache our key assets 15 | self.toolbox.precache( 16 | [ 17 | './build/main.js', 18 | './build/main.css', 19 | './build/polyfills.js', 20 | 'index.html', 21 | 'manifest.json' 22 | ] 23 | ); 24 | 25 | // dynamically cache any other local assets 26 | self.toolbox.router.any('/*', self.toolbox.cacheFirst); 27 | 28 | // for any other requests go to the network, cache, 29 | // and then only use that cached resource if your user goes offline 30 | self.toolbox.router.default = self.toolbox.networkFirst; 31 | -------------------------------------------------------------------------------- /src/theme/variables.scss: -------------------------------------------------------------------------------- 1 | // Ionic Variables and Theming. For more info, please see: 2 | // http://ionicframework.com/docs/v2/theming/ 3 | $font-path: "../assets/fonts"; 4 | 5 | @import "ionic.globals"; 6 | 7 | 8 | // Shared Variables 9 | // -------------------------------------------------- 10 | // To customize the look and feel of this app, you can override 11 | // the Sass variables found in Ionic's source scss files. 12 | // To view all the possible Ionic variables, see: 13 | // http://ionicframework.com/docs/v2/theming/overriding-ionic-variables/ 14 | 15 | 16 | 17 | 18 | // Named Color Variables 19 | // -------------------------------------------------- 20 | // Named colors makes it easy to reuse colors on various components. 21 | // It's highly recommended to change the default colors 22 | // to match your app's branding. Ionic uses a Sass map of 23 | // colors so you can add, rename and remove colors as needed. 24 | // The "primary" color is the only required color in the map. 25 | 26 | $colors: ( 27 | primary: #488aff, 28 | secondary: #32db64, 29 | danger: #f53d3d, 30 | light: #f4f4f4, 31 | dark: #222 32 | ); 33 | 34 | 35 | // App iOS Variables 36 | // -------------------------------------------------- 37 | // iOS only Sass variables can go here 38 | 39 | 40 | 41 | 42 | // App Material Design Variables 43 | // -------------------------------------------------- 44 | // Material Design only Sass variables can go here 45 | 46 | 47 | 48 | 49 | // App Windows Variables 50 | // -------------------------------------------------- 51 | // Windows only Sass variables can go here 52 | 53 | 54 | 55 | 56 | // App Theme 57 | // -------------------------------------------------- 58 | // Ionic apps can have different themes applied, which can 59 | // then be future customized. This import comes last 60 | // so that the above variables are used and Ionic's 61 | // default are overridden. 62 | 63 | @import "ionic.theme.default"; 64 | 65 | 66 | // Ionicons 67 | // -------------------------------------------------- 68 | // The premium icon font for Ionic. For more info, please see: 69 | // http://ionicframework.com/docs/v2/ionicons/ 70 | 71 | @import "ionic.ionicons"; 72 | 73 | 74 | // Fonts 75 | // -------------------------------------------------- 76 | 77 | @import "roboto"; 78 | @import "noto-sans"; 79 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "lib": [ 8 | "dom", 9 | "es2015" 10 | ], 11 | "module": "es2015", 12 | "moduleResolution": "node", 13 | "sourceMap": true, 14 | "target": "es5" 15 | }, 16 | "include": [ 17 | "src/**/*.ts" 18 | ], 19 | "exclude": [ 20 | "node_modules" 21 | ], 22 | "compileOnSave": false, 23 | "atom": { 24 | "rewriteTsconfig": false 25 | }, 26 | "files": [ 27 | "typings/index.d.ts" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-duplicate-variable": true, 4 | "no-unused-variable": [ 5 | true 6 | ] 7 | }, 8 | "rulesDirectory": [ 9 | "node_modules/tslint-eslint-rules/dist/rules" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "globalDependencies": { 3 | "jquery": "registry:dt/jquery#1.10.0+20170310222111" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /typings/globals/jquery/typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "resolution": "main", 3 | "tree": { 4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/f4fdcaca9c94f90442dcedb0c8a84399c47e731f/jquery/index.d.ts", 5 | "raw": "registry:dt/jquery#1.10.0+20170310222111", 6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/f4fdcaca9c94f90442dcedb0c8a84399c47e731f/jquery/index.d.ts" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /typings/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | --------------------------------------------------------------------------------