├── .gitignore ├── package.json ├── LICENSE ├── README.md ├── index.html ├── pull-to-reload.min.js └── pull-to-reload.js /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/* -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pull-to-reload", 3 | "version": "1.1.5", 4 | "description": "This is a pull to refresh implementation for the web. Designed to work with both mobile and desktop.", 5 | "main": "pull-to-reload.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/ErlendEllingsen/pull-to-reload.git" 12 | }, 13 | "keywords": [ 14 | "pull", 15 | "refresh", 16 | "web", 17 | "mobile", 18 | "html", 19 | "pull-to-refresh", 20 | "pull", 21 | "to", 22 | "refresh", 23 | "loading", 24 | "lod", 25 | "pull", 26 | "to", 27 | "load", 28 | "pull-to-load" 29 | ], 30 | "author": "Erlend Ellingsen ", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/ErlendEllingsen/pull-to-reload/issues" 34 | }, 35 | "homepage": "https://github.com/ErlendEllingsen/pull-to-reload#readme" 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Erlend Ellingsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ☝️👇 pull-to-reload 2 | [![npm version](https://badge.fury.io/js/pull-to-reload.svg)](https://badge.fury.io/js/pull-to-reload) [![MIT Licence](https://badges.frapsoft.com/os/mit/mit.svg?v=103)](https://opensource.org/licenses/mit-license.php) 3 | 4 | This is a pull to refresh implementation for the web. Designed to work with both mobile and desktop devices. Fits nicely with web-apps or single-page applications (SPA). *Configurable to the seventh degree.* 5 | 6 | ## 🌵Preview 7 | 8 | Functionality demo 9 | 10 | ### Demo: 11 | [https://erlendellingsen.github.io/pull-to-reload/](https://erlendellingsen.github.io/pull-to-reload/) 12 | 13 | 14 | ## Install 15 | 16 | ### NPM (Recommended) 17 | 18 | `npm install pull-to-reload` 19 | 20 | ### Direct ([Download](https://github.com/ErlendEllingsen/pull-to-reload/releases)) 21 | 22 | Add `pull-to-reload.js` to your project. 23 | 24 | ## 🌿Usage 25 | Quick example: 26 | 27 | **Html** 28 | 29 | ```html 30 |
31 | ... 32 |
33 | 34 |
35 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolores doloribus harum sed odit optio, fuga nam modi quod beatae? Tempore sunt molestiae, soluta quas unde exercitationem, modi accusamus pariatur reiciendis! 36 |
37 | ``` 38 | 39 | **Javascript** 40 | 41 | ```javascript 42 | $(document).ready(function(){ 43 | var ptr = new PullToReload({ 44 | 'callback-loading': function(){ 45 | setTimeout(function(){ 46 | ptr.loadingEnd(); 47 | }, 5000); 48 | } 49 | }); 50 | }); 51 | 52 | ``` 53 | 54 | 55 | Available options: 56 | 57 | ```javascript 58 | this.opts = { 59 | 'refresh-element': 'ptr', //Required 60 | 'content-element': 'content', //Required 61 | 'border-height': 1, 62 | 'height': 80, 63 | 'font-size': '30px', 64 | 'threshold': 20, 65 | 'pre-content': '...', 66 | 'loading-content': 'Loading...', 67 | 'callback-loading': function(){ setTimeout(function(){ self.loadingEnd(); }, 1000); } //Required 68 | } 69 | ``` 70 | 71 | 72 | ## 📎 Dependencies 73 | *None* 🔥 74 | 75 | ## 🥇 Contributors 76 | * NathanHeffley *(Removed jQuery dependency)* 77 | * mdczaplicki *(Minified version)* 78 | 79 | ## 💪🏽 Contribute 80 | If you'd like to contribute to this project you can do so by creating a *fork* and send in a *pull-request*. 81 | 82 | Make sure to write detailed comments and state your changes when sending in a PR. Keep the code style equal to the current. 83 | 84 | Contributions are very much appreciated 😍! 85 | 86 | ### Topics where help is wanted/needed 87 | * Code: Making *pull-to-reload* **not** block regular scrolling functionality (e.g. in mobile apps). 88 | * Documentation/Wiki - Detailed how to/setup 89 | 90 | ## License 91 | As most of my other projects, this project is licensed as **MIT**. 92 | 93 | 94 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 16 | 17 | 32 | 33 | 34 | 35 | 36 |
37 | ... 38 |
39 | 40 |
41 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolores doloribus harum sed odit optio, fuga nam modi quod beatae? Tempore sunt molestiae, soluta quas unde exercitationem, modi accusamus pariatur reiciendis!

42 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolores doloribus harum sed odit optio, fuga nam modi quod beatae? Tempore sunt molestiae, soluta quas unde exercitationem, modi accusamus pariatur reiciendis!

43 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolores doloribus harum sed odit optio, fuga nam modi quod beatae? Tempore sunt molestiae, soluta quas unde exercitationem, modi accusamus pariatur reiciendis!

44 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolores doloribus harum sed odit optio, fuga nam modi quod beatae? Tempore sunt molestiae, soluta quas unde exercitationem, modi accusamus pariatur reiciendis!

45 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolores doloribus harum sed odit optio, fuga nam modi quod beatae? Tempore sunt molestiae, soluta quas unde exercitationem, modi accusamus pariatur reiciendis!

46 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolores doloribus harum sed odit optio, fuga nam modi quod beatae? Tempore sunt molestiae, soluta quas unde exercitationem, modi accusamus pariatur reiciendis!

47 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolores doloribus harum sed odit optio, fuga nam modi quod beatae? Tempore sunt molestiae, soluta quas unde exercitationem, modi accusamus pariatur reiciendis!

48 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolores doloribus harum sed odit optio, fuga nam modi quod beatae? Tempore sunt molestiae, soluta quas unde exercitationem, modi accusamus pariatur reiciendis!

49 |
50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /pull-to-reload.min.js: -------------------------------------------------------------------------------- 1 | var PullToReload=function(optsUser){var self=this;this.opts={"refresh-element":"ptr","content-element":"content","border-height":1,height:80,"font-size":"30px",threshold:20,"pre-content":"...","loading-content":"Loading...","callback-loading":function(){setTimeout(function(){self.loadingEnd()},1e3)}};for(var prop in self.opts){if(optsUser[prop]!==undefined){self.opts[prop]=optsUser[prop]}}this.ptr=document.querySelector("#"+self.opts["refresh-element"]);this.content=document.querySelector("#"+self.opts["content-element"]);this.ptr.style.padding="0px";this.ptr.style.margin="0px";this.ptr.style.display="block";this.ptr.style.height=self.opts.height+"px";this.ptr.style.border=self.opts["border-height"]+"px solid #000";this.ptr.style.borderTop="0px";this.ptr.style.borderLeft="0px";this.ptr.style.borderRight="0px";this.ptr.style.textAlign="center";this.ptr.style.lineHeight=self.opts.height+"px";this.ptr.style.fontSize=self.opts["font-size"];this.ptr.style.marginTop="-"+(self.opts["border-height"]+self.opts.height)+"px";this.loadingStart=function(){this.ptr.innerHTML=self.opts["loading-content"];self.opts["callback-loading"]()};this.loadingEnd=function(){this.ptr.innerHTML=self.opts["pre-content"];this.ptr.style.marginTop="-"+(self.opts["border-height"]+self.opts.height+"px")};this.getPageY=function(event){if(event.pageY===undefined&&event.touches!==undefined){if(event.touches.length<=0){return false}event.pageY=event.touches[event.touches.length-1].pageY}return event.pageY};this.isDragging=false;this.isThresholdReached=false;this.posStart=0;self.content.addEventListener("touchstart",function(event){self.mouseStart(event)});self.content.addEventListener("mousedown",function(event){self.mouseStart(event)});this.mouseStart=function(event){if(document.body.scrollTop>=self.content.getBoundingClientRect().top){return}self.isDragging=true;self.isThresholdReached=false;self.posStart=self.getPageY(event)};document.addEventListener("touchmove",function(event){self.mouseMove(event)});document.addEventListener("mousemove",function(event){self.mouseMove(event)});this.mouseMove=function(event){if(document.body.scrollTop>=self.content.getBoundingClientRect().top){return}if(!self.isDragging){return}event.pageY=self.getPageY(event);if(event.pageY===false){return}var dragDistance=event.pageY-self.posStart;if(dragDistance<=0){return}event.preventDefault();event.stopImmediatePropagation();var newMargin=self.opts["border-height"]+(self.opts.height-dragDistance);if(newMargin<=0){return}if(newMargin<=self.opts.threshold){self.isThresholdReached=true}self.ptr.style.marginTop="-"+(newMargin+"px")};document.addEventListener("touchend",function(event){self.mouseEnd(event)});document.addEventListener("mouseup",function(event){self.mouseEnd(event)});this.mouseEnd=function(event){if(document.body.scrollTop>=self.content.getBoundingClientRect().top){return}if(!self.isDragging){return}event.preventDefault();event.stopImmediatePropagation();if(self.isThresholdReached){self.ptr.style.marginTop="0px";self.isDragging=false;self.isThresholdReached=false;self.loadingStart();return}self.ptr.style.marginTop="-"+(self.opts["border-height"]+self.opts.height+"px");self.isDragging=false;self.isThresholdReached=false}}; -------------------------------------------------------------------------------- /pull-to-reload.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * pull-to-reload 4 | * 5 | * A pull-to-reload system that is compatible with both both web and mobile. 6 | * Configurable with loads of options. 7 | * 8 | * @author Erlend Ellingsen 9 | * @copyright MIT, Erlend Ellingsen 10 | * @version 1.1.5 08.03.2017 11 | */ 12 | 13 | var PullToReload = function (optsUser) { 14 | var self = this; 15 | 16 | // --- OPTIONS --- 17 | this.opts = { 18 | 'refresh-element': 'ptr', // Required 19 | 'content-element': 'content', // Required 20 | 'border-height': 1, 21 | height: 80, 22 | 'font-size': '30px', 23 | threshold: 20, 24 | 'pre-content': '...', 25 | 'loading-content': 'Loading...', 26 | 'callback-loading': function () { 27 | setTimeout(function () { 28 | self.loadingEnd(); 29 | }, 1000); 30 | } // Required 31 | }; 32 | 33 | // Overwrite options with user-options if present 34 | for (var prop in self.opts) { 35 | if (optsUser[prop] !== undefined) { 36 | self.opts[prop] = optsUser[prop]; 37 | } 38 | } 39 | 40 | // --- INIT CODE --- 41 | this.ptr = document.querySelector('#' + self.opts['refresh-element']); 42 | this.content = document.querySelector('#' + self.opts['content-element']); 43 | 44 | // --- STYLING --- 45 | 46 | // Set style 47 | this.ptr.style.padding = '0px'; 48 | this.ptr.style.margin = '0px'; 49 | this.ptr.style.display = 'block'; 50 | this.ptr.style.height = self.opts.height + 'px'; 51 | this.ptr.style.border = self.opts['border-height'] + 'px solid #000'; 52 | this.ptr.style.borderTop = '0px'; 53 | this.ptr.style.borderLeft = '0px'; 54 | this.ptr.style.borderRight = '0px'; 55 | this.ptr.style.textAlign = 'center'; 56 | this.ptr.style.lineHeight = self.opts.height + 'px'; 57 | this.ptr.style.fontSize = self.opts['font-size']; 58 | 59 | // Hide the margin 60 | this.ptr.style.marginTop = '-' + (self.opts['border-height'] + self.opts.height) + 'px'; 61 | 62 | // --- CODE --- 63 | 64 | // --- CODE: HANDLING --- 65 | this.loadingStart = function () { 66 | this.ptr.innerHTML = self.opts['loading-content']; 67 | 68 | self.opts['callback-loading'](); // Call callback 69 | // end loadingStart 70 | }; 71 | 72 | this.loadingEnd = function () { 73 | this.ptr.innerHTML = self.opts['pre-content']; 74 | this.ptr.style.marginTop = '-' + (self.opts['border-height'] + self.opts.height + 'px'); 75 | 76 | // end loadingEnd 77 | }; 78 | 79 | // --- CODE: COMMON FUNCS --- 80 | this.getPageY = function (event) { 81 | if (event.pageY === undefined && event.touches !== undefined) { 82 | if (event.touches.length <= 0) { 83 | return false; 84 | } 85 | event.pageY = event.touches[event.touches.length - 1].pageY; 86 | } 87 | return event.pageY; 88 | 89 | // end getPageY 90 | }; 91 | 92 | // --- CODE: EVENTS --- 93 | 94 | // EVENT: MOUSEUP 95 | this.isDragging = false; 96 | this.isThresholdReached = false; 97 | this.posStart = 0; 98 | 99 | self.content.addEventListener('touchstart', function (event) { 100 | self.mouseStart(event); 101 | }); 102 | 103 | self.content.addEventListener('mousedown', function (event) { 104 | self.mouseStart(event); 105 | }); 106 | 107 | this.mouseStart = function (event) { 108 | 109 | //Prevent PTR if position is beyond content start. 110 | if (document.body.scrollTop >= self.content.getBoundingClientRect().top) { 111 | return; 112 | } 113 | 114 | self.isDragging = true; 115 | self.isThresholdReached = false; 116 | 117 | self.posStart = self.getPageY(event); 118 | 119 | // end mousedown touchstart 120 | }; 121 | 122 | // EVENT: MOUSEUP 123 | document.addEventListener('touchmove', function (event) { 124 | self.mouseMove(event); 125 | }); 126 | 127 | document.addEventListener('mousemove', function (event) { 128 | self.mouseMove(event); 129 | }); 130 | 131 | this.mouseMove = function (event) { 132 | 133 | //Prevent PTR if position is beyond content start. 134 | if (document.body.scrollTop >= self.content.getBoundingClientRect().top) { 135 | return; 136 | } 137 | 138 | if (!self.isDragging) { 139 | return; 140 | } 141 | 142 | // Calculate the drag distance 143 | // Android / Chrome compability. Sometimes consists of a list of touches. 144 | event.pageY = self.getPageY(event); 145 | if (event.pageY === false) { 146 | return; 147 | } 148 | 149 | var dragDistance = (event.pageY - self.posStart); 150 | 151 | if (dragDistance <= 0) { 152 | return; 153 | } // Do not inverse the drag.. 154 | 155 | //Prevent defaults after dragDistance-check. (We still want to keep site scrolling functionality.) 156 | event.preventDefault(); 157 | event.stopImmediatePropagation(); 158 | 159 | var newMargin = (self.opts['border-height'] + (self.opts.height - dragDistance)); 160 | if (newMargin <= 0) { 161 | return; 162 | } 163 | 164 | // Update 165 | if (newMargin <= self.opts.threshold) { 166 | self.isThresholdReached = true; 167 | } 168 | 169 | self.ptr.style.marginTop = '-' + (newMargin + 'px'); 170 | 171 | // end mousemove touchmove 172 | }; 173 | 174 | // EVENT: MOUSEUP 175 | document.addEventListener('touchend', function (event) { 176 | self.mouseEnd(event); 177 | }); 178 | document.addEventListener('mouseup', function (event) { 179 | self.mouseEnd(event); 180 | }); 181 | 182 | this.mouseEnd = function (event) { 183 | 184 | //Prevent PTR if position is beyond content start. 185 | if (document.body.scrollTop >= self.content.getBoundingClientRect().top) { 186 | return; 187 | } 188 | 189 | if (!self.isDragging) { 190 | return; 191 | } 192 | 193 | event.preventDefault(); 194 | event.stopImmediatePropagation(); 195 | 196 | if (self.isThresholdReached) { 197 | // Set margin to show entire 198 | self.ptr.style.marginTop = '0px'; 199 | 200 | self.isDragging = false; 201 | self.isThresholdReached = false; 202 | 203 | self.loadingStart(); 204 | return; 205 | } 206 | 207 | // Reset margin 208 | self.ptr.style.marginTop = '-' + (self.opts['border-height'] + self.opts.height + 'px'); 209 | 210 | self.isDragging = false; 211 | self.isThresholdReached = false; 212 | 213 | // end mouseup touchend 214 | }; 215 | 216 | // end PullToReload 217 | }; 218 | --------------------------------------------------------------------------------