├── bower.json ├── LICENSE ├── xpull.css ├── index.html ├── README.md └── xpull.js /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xpull", 3 | "main": ["xpull.js","xpull.css"], 4 | "version": "1.0.0", 5 | "homepage": "https://github.com/sjovanovic/xpull", 6 | "authors": [ 7 | "sjovanovic " 8 | ], 9 | "description": "Pull to refresh jQuery plugin for iOS and Android browsers and web views", 10 | "keywords": [ 11 | "pull", 12 | "to", 13 | "refresh", 14 | "android", 15 | "ios", 16 | "angular", 17 | "release", 18 | "reload", 19 | "jquery", 20 | "plugin", 21 | "directive" 22 | ], 23 | "license": "MIT", 24 | "ignore": [ 25 | "**/.*", 26 | "node_modules", 27 | "bower_components", 28 | "test", 29 | "tests" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Slobodan Jovanovic 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. -------------------------------------------------------------------------------- /xpull.css: -------------------------------------------------------------------------------- 1 | /* 2 | Xpull - pull to refresh jQuery plugin for iOS and Android 3 | */ 4 | .xpull { 5 | display: none; 6 | height: 40px; 7 | margin: 0 auto; 8 | position: relative; 9 | text-align: center; 10 | transform: translate3d(0, 0, 0) rotate(0deg); 11 | } 12 | 13 | .xpull_pulled .xpull__arrow { 14 | top: 5px; 15 | transform: rotate(180deg); 16 | } 17 | 18 | .xpull__start-msg-text { 19 | margin-bottom: 5px; 20 | } 21 | 22 | .xpull__arrow { 23 | position: relative; 24 | width: 4px; 25 | height: 10px; 26 | margin: 0 auto 6px; 27 | background-color: #cacaca; 28 | border-top-left-radius: 3px; 29 | border-top-right-radius: 3px; 30 | transition: transform 300ms ease; 31 | } 32 | 33 | .xpull__arrow::after { 34 | content: ''; 35 | position: absolute; 36 | top: 100%; 37 | left: -3px; 38 | width: 0; 39 | height: 0; 40 | border-left: 5px solid transparent; 41 | border-right: 5px solid transparent; 42 | border-top: 6px solid #cacaca; 43 | } 44 | 45 | .xpull__spinner { 46 | display: none; 47 | padding-top: 10px; 48 | } 49 | 50 | .xpull__spinner-circle { 51 | height: 25px; 52 | width: 25px; 53 | margin: 0 auto; 54 | position: relative; 55 | left: -4px; 56 | animation: rotation 0.6s infinite linear; 57 | border: 4px solid rgba(202, 202, 202, 0.15); 58 | border-top: 4px solid rgba(202, 202, 202, 0.9); 59 | border-radius: 100%; 60 | } 61 | 62 | @-webkit-keyframes rotation { 63 | from {-webkit-transform: rotate(0deg);} 64 | to {-webkit-transform: rotate(359deg);} 65 | } 66 | 67 | @-moz-keyframes rotation { 68 | from {-moz-transform: rotate(0deg);} 69 | to {-moz-transform: rotate(359deg);} 70 | } 71 | 72 | @-o-keyframes rotation { 73 | from {-o-transform: rotate(0deg);} 74 | to {-o-transform: rotate(359deg);} 75 | } 76 | 77 | @keyframes rotation { 78 | from {transform: rotate(0deg);} 79 | to {transform: rotate(359deg);} 80 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Xpull demo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 28 | 29 | 47 | 48 | 49 | 50 |
51 |
52 |

Xpull demo

53 |

pull to refresh jQuery plugin for touch devices

54 |
55 |

Pull the below list to refresh.

56 |
57 | 58 | 59 |
60 |
61 |
Pull Down & Release to Refresh
62 |
63 |
64 |
65 |
66 |
67 |
68 | 69 | 70 |
    71 |
  • Item
  • 72 |
  • Item
  • 73 |
  • Item
  • 74 |
  • Item
  • 75 |
  • Item
  • 76 |
  • Item
  • 77 |
  • Item
  • 78 |
  • Item
  • 79 |
  • Item
  • 80 |
  • Item
  • 81 |
  • Item
  • 82 |
  • Item
  • 83 |
  • Item
  • 84 |
  • Item
  • 85 |
  • Item
  • 86 |
  • Item
  • 87 |
  • Item
  • 88 |
  • Item
  • 89 |
  • Item
  • 90 |
  • Item
  • 91 |
  • Item
  • 92 |
  • Item
  • 93 |
  • Item
  • 94 |
  • Item
  • 95 |
  • Item
  • 96 |
  • Item
  • 97 |
  • Item
  • 98 |
  • Item
  • 99 |
  • Item
  • 100 |
  • Item
  • 101 |
  • Item
  • 102 |
  • Item
  • 103 |
  • Item
  • 104 |
  • Item
  • 105 |
  • Item
  • 106 |
  • Item
  • 107 |
  • Item
  • 108 |
  • Item
  • 109 |
  • Item
  • 110 |
  • Item
  • 111 |
112 |
113 | 114 |
115 | 116 | 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | xpull 2 | ===== 3 | 4 | Pull to refresh jQuery plugin for touch devices. 5 | 6 | This is one of the pull to refresh plugins for the web. What distinguishes it from the others is that it is lightweight, extremely easy to use and optimized for Android (mobile Chrome browser) and iOS (mobile safari). It also emulates elastic scroll on both platforms. 7 | All animations and graphics are pure CSS3. Angular friendly. 8 | 9 | ### Demo: 10 | 11 | Open [sjovanovic.github.io/xpull/demo.html](http://sjovanovic.github.io/xpull/demo.html) on your iOS or Android device 12 | 13 | ### Install 14 | 15 | Just include xpull.js and xpull.css in your folder. 16 | Or install via Bower: 17 | 18 | ``` 19 | bower install xpull 20 | ``` 21 | 22 | 23 | ### Usage: 24 | 25 | Include xpull.css and xpull.js in your page 26 | ``` 27 | 28 | 29 | ``` 30 | 31 | Add html markup on your page in element where need Pull to refresh feature 32 | ``` 33 |
34 |
35 |
Pull Down & Release to Refresh
36 |
37 |
38 |
39 |
40 |
41 |
42 | ``` 43 | Simple for change and styling use HTML and CSS. 44 | Classes used in plugin: 45 | ``` 46 | .xpull - container for plugin element 47 | .xpull_pulled - add class to .xpull when user pulls the content over pull threshold 48 | .xpull__start-msg - element show when user pulls the content 49 | .xpull__spinner - loader 50 | ``` 51 | 52 | Init plugin with options 53 | ``` 54 | $('#container').xpull({ 55 | 'callback':function(){ 56 | console.log('Released...'); 57 | } 58 | }); 59 | ``` 60 | 61 | ### Options: 62 | 63 | ``` 64 | { 65 | paused: false, // Is the pulling paused ? 66 | pullThreshold: 50, // Pull threshold - amount in pixels required to pull to enable release callback 67 | maxPullThreshold: 50, // Max pull down element - amount in pixels 68 | spinnerTimeout: 2000 // timeout in miliseconds after which the loading indicator stops spinning. If set to 0 - the loading will be indefinite 69 | scrollingDom: $(selector), // For arbitrary nesting. The scrolling element is not the same as element that pulls-down on reload. 70 | onPullStart: function(){}, // triggers after user start pulls the content 71 | onPullEnd: function(){}, // triggers after user end pulls the content 72 | callback: function(){}, // triggers after user pulls the content over pull threshold and releases 73 | } 74 | ``` 75 | 76 | To get the instance of Xpull: 77 | 78 | ``` 79 | var xpull = $('selector').data("plugin_xpull"); 80 | ``` 81 | 82 | ### Methods: 83 | 84 | * `reset()` - cancels he spinning and resets the plugin to initial state. Example: `$('#container').data('plugin_xpull').reset();` 85 | * `destroy()` - destroy plugin and go to initial state. Example: `$('#container').data('plugin_xpull').destroy();` 86 | 87 | ### Pausing: 88 | 89 | * You can simply pause the handling of pull event, by simply setting the value of *paused* property, e.g.: 90 | `$('#container').data('plugin_xpull').options.paused = true; // or: false` 91 | 92 | ### Angular 93 | 94 | Xpull is also easy to use in Angular. Just use this example: 95 | 96 | ``` 97 | /* 98 | Xpull - pull to refresh jQuery plugin as Angular directive 99 | */ 100 | angular.module("xpull").directive("ngXpull", function() { 101 | return function(scope, elm, attr) { 102 | return $(elm[0]).xpull({ 103 | 'callback': function() { 104 | return scope.$apply(attr.ngXpull); 105 | } 106 | }); 107 | }; 108 | }); 109 | ``` 110 | Then in view add as attribute on your scrollable container: 111 | 112 | ``` 113 |
114 | ``` 115 | -------------------------------------------------------------------------------- /xpull.js: -------------------------------------------------------------------------------- 1 | /* 2 | Xpull - pull to refresh jQuery plugin for touch devices 3 | by Slobodan Jovanovic 4 | 5 | Usage: 6 | 7 | $('selector').xpull(options); 8 | 9 | Example 10 | 11 | $('#container').xpull({ 12 | 'callback':function(){ 13 | console.log('Released...'); 14 | } 15 | }); 16 | 17 | Options: 18 | 19 | { 20 | paused: false, // Is the pulling paused ? 21 | pullThreshold: 50, // Pull threshold - amount in pixels required to pull to enable release callback 22 | maxPullThreshold: 50, // Max pull down element - amount in pixels 23 | spinnerTimeout: 2000 // timeout in miliseconds after which the loading indicator stops spinning. If set to 0 - the loading will be indefinite 24 | onPullStart: function(){}, // triggers after user start pulls the content 25 | onPullEnd: function(){}, // triggers after user end pulls the content 26 | callback: function(){}, // triggers after user pulls the content over pull threshold and releases 27 | } 28 | 29 | To get the thatance of Xpull: 30 | 31 | var xpull = $('selector').data("plugin_xpull"); 32 | 33 | Methods: 34 | 35 | reset() - cancels he spinning and resets the plugin to initial state. Example: $('#container').data('plugin_xpull').reset(); 36 | destroy() - destroy plugin and go to initial state. Example: $('#container').data('plugin_xpull').destroy(); 37 | 38 | */ 39 | ; 40 | (function($, window, document) { 41 | var pluginName = 'xpull', 42 | defaults = { 43 | paused: false, 44 | pullThreshold: 50, 45 | maxPullThreshold: 50, 46 | spinnerTimeout: 2000, 47 | scrollingDom: null, // if null, specified element 48 | onPullStart: function() {}, 49 | onPullEnd: function() {}, 50 | onPullThreshold: function() {}, 51 | onPullThresholdReverse: function() {}, 52 | callback: function() {} 53 | }; 54 | 55 | function Plugin (element, options) { 56 | this.element = element; 57 | this.options = $.extend({}, defaults, options); 58 | this._defaults = defaults; 59 | this._name = pluginName; 60 | this.init(); 61 | } 62 | Plugin.prototype = { 63 | init: function() { 64 | var ofstop = 0, 65 | that = this, 66 | fingerOffset = 0, 67 | top = 0, 68 | hasc = false, 69 | elm = {}; 70 | 71 | that.$element = $(that.element); 72 | that.elm = elm = that.$element.children(':not(.xpull)'); 73 | that.indicator = that.$element.find('.xpull:eq(0)'); 74 | that.indicator.css({display: 'block'}); 75 | that.spinner = that.indicator.find('.xpull__spinner:eq(0)'); 76 | that.startBlock = that.indicator.find('.xpull__start-msg:eq(0)'); 77 | that.indicatorHeight = that.indicator.outerHeight(); 78 | 79 | that._changeStyle(-that.indicatorHeight, true); 80 | that.$element.css({ 81 | '-webkit-overflow-scrolling': 'touch', 82 | 'overflow-scrolling': 'touch' 83 | }); 84 | 85 | ofstop = that.$element.offset().top; 86 | 87 | that.elast = true; 88 | that.startBlock.css('visibility', 'hidden'); 89 | that.indicatorHidden = true; 90 | elm.unbind('touchstart.' + pluginName); 91 | elm.on('touchstart.' + pluginName, function (ev) { 92 | if (that.options.paused) { 93 | return false; 94 | } 95 | 96 | that.options.onPullStart.call(this); 97 | fingerOffset = ev.originalEvent.touches[0].pageY - ofstop; 98 | }); 99 | elm.unbind('touchmove.' + pluginName); 100 | elm.on('touchmove.' + pluginName, function(ev) { 101 | 102 | if (that.options.paused) { 103 | return false; 104 | } 105 | 106 | if (elm.position().top < 0 || (that.options.scrollingDom || that.$element).scrollTop() > 0 || document.body.scrollTop > 0) { // trigger callback only if pulled from the top of the list 107 | return true; 108 | } 109 | 110 | if (that.indicatorHidden) { 111 | that.startBlock.css('visibility', 'visible'); 112 | that.indicatorHidden = false; 113 | } 114 | top = (ev.originalEvent.touches[0].pageY - ofstop - fingerOffset); 115 | 116 | if (top > 1) { 117 | 118 | if (that.elast) { 119 | $(document.body).on('touchmove.' + pluginName, function(e) { 120 | e.preventDefault(); 121 | }); 122 | that.elast = false; 123 | } 124 | 125 | if (top <= (parseInt(that.options.pullThreshold) + that.options.maxPullThreshold)) { 126 | that._changeStyle((top - that.indicatorHeight), false); 127 | } 128 | 129 | if (top > that.options.pullThreshold && !hasc) { 130 | that.indicator.addClass('xpull_pulled'); 131 | that.options.onPullThreshold.call(this); 132 | } else if (top <= that.options.pullThreshold && hasc) { 133 | that.indicator.removeClass('xpull_pulled'); 134 | that.options.onPullThresholdReverse.call(this); 135 | } 136 | 137 | } else { 138 | $(document.body).unbind('touchmove.' + pluginName); 139 | that.elast = true; 140 | } 141 | hasc = that.indicator.hasClass('xpull_pulled'); 142 | 143 | }); 144 | elm.unbind('touchend.' + pluginName); 145 | elm.on('touchend.' + pluginName, function () { 146 | 147 | if (that.options.paused) { 148 | return false; 149 | } 150 | 151 | that.options.onPullEnd.call(this); 152 | if (top > 0) { 153 | if (top > that.options.pullThreshold) { 154 | that.options.callback.call(this); 155 | that.startBlock.hide(); 156 | that.spinner.show(); 157 | that.options.paused = true; 158 | 159 | that._changeStyle(0, true); 160 | 161 | if (that.options.spinnerTimeout) { 162 | setTimeout(function () { 163 | that.reset(); 164 | }, that.options.spinnerTimeout); 165 | } 166 | 167 | } else { 168 | that._changeStyle(-that.indicatorHeight, true); 169 | } 170 | top = 0; 171 | } 172 | if (!that.indicatorHidden) { 173 | that.startBlock.css('visibility', 'hidden'); 174 | that.indicatorHidden = true; 175 | } 176 | setTimeout(function() { 177 | elm.css({ 178 | 'transition': '' 179 | }); 180 | that.indicator.css({ 181 | 'transition': '' 182 | }); 183 | $(document.body).unbind('touchmove.' + pluginName); 184 | that.elast = true; 185 | }, 300); 186 | }); 187 | }, 188 | 189 | // manipulate styles for elements 190 | _changeStyle: function (top, transition) { 191 | var changeCss = { 192 | transform: 'translate3d(0px, ' + top + 'px, 0px)', 193 | transition: 'transform 300ms ease' 194 | }; 195 | 196 | if (!transition) { 197 | changeCss.transition = ''; 198 | } 199 | 200 | this.indicator.css(changeCss); 201 | this.elm.css(changeCss); 202 | }, 203 | 204 | // reset 205 | reset: function() { 206 | var that = this; 207 | 208 | that._changeStyle(-that.indicatorHeight, true); 209 | 210 | setTimeout(function() { 211 | that.startBlock.show(); 212 | that.spinner.hide(); 213 | that.options.paused = false; 214 | that.indicator.removeClass('xpull_pulled'); 215 | 216 | that._changeStyle(-that.indicatorHeight, false); 217 | $(document.body).unbind('touchmove.' + pluginName); 218 | that.elast = true; 219 | }, 300); 220 | }, 221 | 222 | // destroy 223 | destroy: function () { 224 | var that = this, 225 | elm = that.elm; 226 | 227 | // remove styles 228 | that._changeStyle(0); 229 | that.indicator.css({display: 'none'}); 230 | 231 | // off listener 232 | elm.off('touchstart.' + pluginName); 233 | elm.off('touchmove.' + pluginName); 234 | elm.off('touchend.' + pluginName); 235 | $(document.body).off('touchmove.' + pluginName); 236 | 237 | that.$element.removeData('plugin_' + pluginName); 238 | } 239 | }; 240 | $.fn[pluginName] = function(options) { 241 | return this.each(function() { 242 | if (!$.data(this, 'plugin_' + pluginName)) { 243 | $.data(this, 'plugin_' + pluginName, new Plugin(this, options)); 244 | } 245 | }); 246 | }; 247 | 248 | })(jQuery, window, document); 249 | --------------------------------------------------------------------------------