├── .gitignore ├── phaser.min.js ├── lightworld_large.gif ├── Gruntfile.js ├── package.json ├── scrollable-kinetic.html ├── README.md ├── dist ├── phaser-scrollable.min.js └── phaser-scrollable.js └── src └── phaser-scrollable.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /phaser.min.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trueicecold/phaser-scrollable/HEAD/phaser.min.js -------------------------------------------------------------------------------- /lightworld_large.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trueicecold/phaser-scrollable/HEAD/lightworld_large.gif -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | grunt.initConfig({ 4 | pkg: grunt.file.readJSON('package.json'), 5 | concat: { 6 | options: { 7 | separator: ';' 8 | }, 9 | dist: { 10 | src: ['src/*.js'], 11 | dest: 'dist/<%= pkg.name %>.js' 12 | } 13 | }, 14 | uglify: { 15 | options: { 16 | banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n' 17 | }, 18 | dist: { 19 | files: { 20 | 'dist/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>'] 21 | } 22 | } 23 | } 24 | }); 25 | grunt.loadNpmTasks('grunt-contrib-uglify'); 26 | grunt.loadNpmTasks('grunt-contrib-concat'); 27 | 28 | grunt.registerTask('default', ['concat','uglify']); 29 | 30 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phaser-scrollable", 3 | "version": "1.0.0", 4 | "description": "A phaser kinetic scrollable group.", 5 | "main": "./dist/phaser-scrollable.min.js", 6 | "dependencies": { 7 | "grunt": "^1.0.1", 8 | "grunt-contrib-concat": "^1.0.1", 9 | "grunt-contrib-uglify": "^3.3.0", 10 | "phaser": "^2.0.6" 11 | }, 12 | "devDependencies": {}, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/trueicecold/phaser-scrollable.git" 16 | }, 17 | "keywords": [ 18 | "phaser", 19 | "scroll", 20 | "scrollable", 21 | "vertical", 22 | "horizontal", 23 | "mousewheel", 24 | "kinetic" 25 | ], 26 | "author": "Yaniv Nagar", 27 | "license": "MIT", 28 | "licenseUrl": "http://www.opensource.org/licenses/mit-license.php", 29 | "bugs": { 30 | "url": "https://github.com/trueicecold/phaser-scrollable/issues" 31 | }, 32 | "homepage": "https://github.com/trueicecold/phaser-scrollable#readme" 33 | } 34 | -------------------------------------------------------------------------------- /scrollable-kinetic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Phaser scrollable component 2 | 3 | This component can simulate vertical and horizontal scrolling to a Phaser.Group. 4 | 5 | > This component is directly derived from [jdnichollsc](https://github.com/jdnichollsc)'s amazing work on his plugin, [Kinetic Scrolling Plugin](https://github.com/jdnichollsc/Phaser-Kinetic-Scrolling-Plugin). I converted it to use groups instead of moving the camera, added support for multiple scrollers, and some bug fixes. 6 | 7 | 8 | ### Download the Component 9 | 10 | Install using [npm](https://www.npmjs.com/): 11 | 12 | ```bash 13 | npm install https://github.com/trueicecold/phaser-scrollable --save 14 | ``` 15 | 16 | ### Using the Component 17 | 18 | ```javascript 19 | this.scroller = game.add.existing(new ScrollableArea(x, y, w, h, params)); 20 | var textStyle = {font:"30px Arial", fill:"#ffff00"}; 21 | for (var i=0;i<10;i++) { 22 | for (var j=0;j<80;j++) { 23 | var text = game.make.text(i*330, j*30, "Yes, everything scrolls", textStyle); 24 | scroller.addChild(text); 25 | } 26 | } 27 | scroller.start(); 28 | ``` 29 | 30 | * x - the x position of the scrollable container 31 | * y - the y position of the scrollable container 32 | * w - the width of the scrollable container. If the width of the children inside is larger than this, a horizontal scrolling will be possible. 33 | * h - the height of the scrollable container. If the height of the children inside is larger than this, a vertical scrolling will be possible. 34 | * params - configurable parameters for the component: 35 | * horizontalScroll - allow horizontal scroll. Default is true. 36 | * verticalScroll - allow vertical scroll. Default is true. 37 | * horizontalWheel - allow horizontal wheel support. Default is true. Should not be enabled along with verticalWheel. 38 | * verticalWheel - allow vertical wheel support. Default is false. Should not be enabled along with horizontalWheel. 39 | * kineticMovement - use kinetic scrolling. Default is true. Use false to switch to static scrolling (scrolling stops as you leave the touch). 40 | * timeConstantScroll - The rate of deceleration for the scrolling. Default is 325ms. 41 | * deltaWheel - Delta increment of the mouse wheel. 42 | 43 | ### Change the component parameters at runtime 44 | 45 | ```javascript 46 | scroller.configure(params); 47 | ``` 48 | 49 | ### Methods 50 | * start - start the scroller behavior. 51 | * stop - stops the scroller behavior. this does not reset the content position. 52 | * setPosition - object (x,y) - repositions the scroller. 53 | * scrollTo - object (x,y,time,easing,allowScrollStopOnTouch) - scrolls the scroller to the position given. 54 | * time - defaults to 1000 55 | * easing - defaults to Phaser.Easing.Quadratic.Out 56 | * allowScrollStopOnTouch - choose whether to allow the user to cancel the scrolling animation when clicking on the scroller. Defaults to false, useful to cutscenes. 57 | 58 | ### Demo 59 | 60 | [https://cdn.rawgit.com/trueicecold/phaser-scrollable/e2bfe0cd/scrollable-kinetic.html](https://cdn.rawgit.com/trueicecold/phaser-scrollable/e2bfe0cd/scrollable-kinetic.html) 61 | 62 | Made with <3 for -------------------------------------------------------------------------------- /dist/phaser-scrollable.min.js: -------------------------------------------------------------------------------- 1 | /*! phaser-scrollable 14-01-2018 */ 2 | 3 | var ScrollableArea=function(t,i,s,e,h){h=h||{},Phaser.Group.call(this,game),this._x=this.x=t,this._y=this.y=i,this._w=s,this._h=e,this.maskGraphics=game.add.graphics(t,i),this.maskGraphics.beginFill(0),this.maskGraphics.drawRect(0,0,s,e),this.maskGraphics.alpha=.2,this.maskGraphics.inputEnabled=!0,this.mask=this.maskGraphics,this.dragging=!1,this.pressedDown=!1,this.timestamp=0,this.callbackID=0,this.targetX=0,this.targetY=0,this.autoScrollX=!1,this.autoScrollY=!1,this.inputX=0,this.inputY=0,this.startX=0,this.startY=0,this.velocityX=0,this.velocityY=0,this.amplitudeX=0,this.amplitudeY=0,this.directionWheel=0,this.velocityWheelX=0,this.velocityWheelY=0,this.settings={kineticMovement:!0,timeConstantScroll:325,horizontalScroll:!0,verticalScroll:!0,horizontalWheel:!1,verticalWheel:!0,deltaWheel:40,clickXThreshold:5,clickYThreshold:5},this.configure(h),this.addChild=function(t){this.maskGraphics.x=this.parent.x+this._x,this.maskGraphics.y=this.parent.y+this._y,ScrollableArea.prototype.addChild.call(this,t)}};ScrollableArea.prototype=Object.create(Phaser.Group.prototype),ScrollableArea.prototype.constructor=ScrollableArea,ScrollableArea.prototype.configure=function(t){if(t)for(var i in t)this.settings.hasOwnProperty(i)&&null!=this.settings[i]&&(this.settings[i]=t[i])},ScrollableArea.prototype.start=function(){this.game.input.onDown.add(this.beginMove,this),this.callbackID=this.game.input.addMoveCallback(this.moveCanvas,this),this.game.input.onUp.add(this.endMove,this),this.game.input.mouse.mouseWheelCallback=this.mouseWheel.bind(this)},ScrollableArea.prototype.beginMove=function(){this.allowScrollStopOnTouch&&this.scrollTween&&this.scrollTween.pause(),this.maskGraphics.getBounds().contains(this.game.input.x,this.game.input.y)?(this.startedInside=!0,this.startX=this.inputX=this.game.input.x,this.startY=this.inputY=this.game.input.y,this.pressedDown=!0,this.timestamp=Date.now(),this.velocityY=this.amplitudeY=this.velocityX=this.amplitudeX=0):this.startedInside=!1},ScrollableArea.prototype.moveCanvas=function(t,i,s){if(this.pressedDown){this.now=Date.now();var e,h=this.now-this.timestamp;if(this.timestamp=this.now,this.settings.horizontalScroll)0!==(e=i-this.startX)&&(this.dragging=!0),this.startX=i,this.velocityX=1e3*e/(1+h)*.8+.2*this.velocityX,this.x+=e;if(this.settings.verticalScroll)0!==(e=s-this.startY)&&(this.dragging=!0),this.startY=s,this.velocityY=1e3*e/(1+h)*.8+.2*this.velocityY,this.y+=e;this.limitMovement()}},ScrollableArea.prototype.endMove=function(){if(this.startedInside){if(this.pressedDown=!1,this.autoScrollX=!1,this.autoScrollY=!1,!this.settings.kineticMovement)return;if(this.now=Date.now(),this.game.input.activePointer.withinGame&&((this.velocityX>10||this.velocityX<-10)&&(this.amplitudeX=.8*this.velocityX,this.targetX=Math.round(this.x+this.amplitudeX),this.autoScrollX=!0),(this.velocityY>10||this.velocityY<-10)&&(this.amplitudeY=.8*this.velocityY,this.targetY=Math.round(this.y+this.amplitudeY),this.autoScrollY=!0)),this.game.input.activePointer.withinGame||(this.velocityWheelXAbs=Math.abs(this.velocityWheelX),this.velocityWheelYAbs=Math.abs(this.velocityWheelY),this.settings.horizontalScroll&&(this.velocityWheelXAbs<.1||!this.game.input.activePointer.withinGame)&&(this.autoScrollX=!0),this.settings.verticalScroll&&(this.velocityWheelYAbs<.1||!this.game.input.activePointer.withinGame)&&(this.autoScrollY=!0)),Math.abs(game.input.x-this.inputX)<=this.settings.clickXThreshold&&Math.abs(game.input.y-this.inputY)<=this.settings.clickYThreshold)for(var t=0;t0&&this.getChildAt(t).events.onInputUp.dispatch()}},ScrollableArea.prototype.scrollTo=function(t,i,s,e,h){this.scrollTween&&this.scrollTween.pause(),t=t>0?-t:t,i=i>0?-i:i,e=e||Phaser.Easing.Quadratic.Out,s=s||1e3,h=h||!1,this.allowScrollStopOnTouch=h,this.scrollTween=game.add.tween(this),this.scrollTween.to({x:t,y:i},s,e).start()},ScrollableArea.prototype.update=function(){var t;(this.elapsed=Date.now()-this.timestamp,this.velocityWheelXAbs=Math.abs(this.velocityWheelX),this.velocityWheelYAbs=Math.abs(this.velocityWheelY),this.autoScrollX&&0!=this.amplitudeX)&&((t=-this.amplitudeX*Math.exp(-this.elapsed/this.settings.timeConstantScroll))>.5||t<-.5?this.x=this.targetX+t:this.autoScrollX=!1);this.autoScrollY&&0!=this.amplitudeY&&((t=-this.amplitudeY*Math.exp(-this.elapsed/this.settings.timeConstantScroll))>.5||t<-.5?this.y=this.targetY+t:this.autoScrollY=!1);this.autoScrollX||this.autoScrollY||(this.dragging=!1),this.settings.horizontalWheel&&this.velocityWheelXAbs>.1&&(this.dragging=!0,this.amplitudeX=0,this.autoScrollX=!1,this.x+=this.velocityWheelX,this.velocityWheelX*=.95),this.settings.verticalWheel&&this.velocityWheelYAbs>.1&&(this.dragging=!0,this.autoScrollY=!1,this.y+=this.velocityWheelY,this.velocityWheelY*=.95),this.limitMovement()},ScrollableArea.prototype.mouseWheel=function(t){if(this.settings.horizontalWheel||this.settings.verticalWheel){t.preventDefault();var i=120*this.game.input.mouse.wheelDelta/this.settings.deltaWheel;this.directionWheel!=this.game.input.mouse.wheelDelta&&(this.velocityWheelX=0,this.velocityWheelY=0,this.directionWheel=this.game.input.mouse.wheelDelta),this.settings.horizontalWheel&&(this.autoScrollX=!1,this.velocityWheelX+=i),this.settings.verticalWheel&&(this.autoScrollY=!1,this.velocityWheelY+=i)}},ScrollableArea.prototype.stop=function(){this.game.input.onDown.remove(this.beginMove,this),this.callbackID?this.game.input.deleteMoveCallback(this.callbackID):this.game.input.deleteMoveCallback(this.moveCanvas,this),this.game.input.onUp.remove(this.endMove,this),this.game.input.mouse.mouseWheelCallback=null},ScrollableArea.prototype.setPosition=function(t){t.x&&(this.x+=t.x-this._x,this.maskGraphics.x=this._x=t.x),t.y&&(this.y+=t.y-this._y,this.maskGraphics.y=this._y=t.y)},ScrollableArea.prototype.limitMovement=function(){this.settings.horizontalScroll&&(this.x>this._x&&(this.x=this._x),this.x<-(this.width-this._w-this._x)&&(this.width>this._w?this.x=-(this.width-this._w-this._x):this.x=this._x)),this.settings.verticalScroll&&(this.y>this._y&&(this.y=this._y),this.y<-(this.height-this._h-this._y)&&(this.height>this._h?this.y=-(this.height-this._h-this._y):this.y=this._y))}; -------------------------------------------------------------------------------- /src/phaser-scrollable.js: -------------------------------------------------------------------------------- 1 | var ScrollableArea = function(x, y, w, h, params) { 2 | params = params || {}; 3 | 4 | Phaser.Group.call(this, game); 5 | 6 | this._x = this.x = x; 7 | this._y = this.y = y; 8 | this._w = w; 9 | this._h = h; 10 | 11 | this.maskGraphics = game.add.graphics(x, y); 12 | this.maskGraphics.beginFill(0x000000); 13 | this.maskGraphics.drawRect(0, 0, w, h); 14 | this.maskGraphics.alpha = 0.2; 15 | this.maskGraphics.inputEnabled = true; 16 | this.mask = this.maskGraphics; 17 | 18 | this.dragging = false; 19 | this.pressedDown = false; 20 | this.timestamp = 0; 21 | this.callbackID = 0; 22 | 23 | this.targetX = 0; 24 | this.targetY = 0; 25 | 26 | this.autoScrollX = false; 27 | this.autoScrollY = false; 28 | 29 | this.inputX = 0; 30 | this.inputY = 0; 31 | 32 | this.startX = 0; 33 | this.startY = 0; 34 | 35 | this.velocityX = 0; 36 | this.velocityY = 0; 37 | 38 | this.amplitudeX = 0; 39 | this.amplitudeY = 0; 40 | 41 | this.directionWheel = 0; 42 | 43 | this.velocityWheelX = 0; 44 | this.velocityWheelY = 0; 45 | 46 | this.settings = { 47 | kineticMovement: true, 48 | timeConstantScroll: 325, //really mimic iOS 49 | horizontalScroll: true, 50 | verticalScroll: true, 51 | horizontalWheel: false, 52 | verticalWheel: true, 53 | deltaWheel: 40, 54 | clickXThreshold: 5, 55 | clickYThreshold: 5, 56 | }; 57 | 58 | this.configure(params); 59 | 60 | this.addChild = function(child) { 61 | this.maskGraphics.x = this.parent.x + this._x; 62 | this.maskGraphics.y = this.parent.y + this._y; 63 | ScrollableArea.prototype.addChild.call(this, child); 64 | } 65 | } 66 | 67 | ScrollableArea.prototype = Object.create(Phaser.Group.prototype); 68 | ScrollableArea.prototype.constructor = ScrollableArea; 69 | 70 | /** 71 | * Change Default Settings of the plugin 72 | * 73 | * @method ScrollableArea#configure 74 | * @param {Object} [options] - Object that contain properties to change the behavior of the plugin. 75 | * @param {number} [options.timeConstantScroll=325] - The rate of deceleration for the scrolling. 76 | * @param {boolean} [options.kineticMovement=true] - Enable or Disable the kinematic motion. 77 | * @param {boolean} [options.horizontalScroll=true] - Enable or Disable the horizontal scrolling. 78 | * @param {boolean} [options.verticalScroll=false] - Enable or Disable the vertical scrolling. 79 | * @param {boolean} [options.horizontalWheel=true] - Enable or Disable the horizontal scrolling with mouse wheel. 80 | * @param {boolean} [options.verticalWheel=false] - Enable or Disable the vertical scrolling with mouse wheel. 81 | * @param {number} [options.deltaWheel=40] - Delta increment of the mouse wheel. 82 | */ 83 | ScrollableArea.prototype.configure = function (options) { 84 | if (options) { 85 | for (var property in options) { 86 | if (this.settings.hasOwnProperty(property) && this.settings[property] != null) { 87 | this.settings[property] = options[property]; 88 | } 89 | } 90 | } 91 | }; 92 | 93 | /** 94 | * Start the Plugin. 95 | * 96 | * @method ScrollableArea#start 97 | */ 98 | ScrollableArea.prototype.start = function () { 99 | this.game.input.onDown.add(this.beginMove, this); 100 | this.callbackID = this.game.input.addMoveCallback(this.moveCanvas, this); 101 | this.game.input.onUp.add(this.endMove, this); 102 | this.game.input.mouse.mouseWheelCallback = this.mouseWheel.bind(this); 103 | }; 104 | 105 | /** 106 | * Event triggered when a pointer is pressed down, resets the value of variables. 107 | */ 108 | ScrollableArea.prototype.beginMove = function () { 109 | if (this.allowScrollStopOnTouch && this.scrollTween) { 110 | this.scrollTween.pause(); 111 | } 112 | 113 | if (this.maskGraphics.getBounds().contains(this.game.input.x, this.game.input.y)) { 114 | this.startedInside = true; 115 | 116 | this.startX = this.inputX = this.game.input.x; 117 | this.startY = this.inputY = this.game.input.y; 118 | this.pressedDown = true; 119 | this.timestamp = Date.now(); 120 | this.velocityY = this.amplitudeY = this.velocityX = this.amplitudeX = 0; 121 | } 122 | else { 123 | this.startedInside = false; 124 | } 125 | }; 126 | 127 | /** 128 | * Event triggered when the activePointer receives a DOM move event such as a mousemove or touchmove. 129 | * The camera moves according to the movement of the pointer, calculating the velocity. 130 | */ 131 | ScrollableArea.prototype.moveCanvas = function (pointer, x, y) { 132 | if (!this.pressedDown) return; 133 | 134 | this.now = Date.now(); 135 | var elapsed = this.now - this.timestamp; 136 | this.timestamp = this.now; 137 | 138 | if (this.settings.horizontalScroll) { 139 | var delta = x - this.startX; //Compute move distance 140 | if (delta !== 0) this.dragging = true; 141 | this.startX = x; 142 | this.velocityX = 0.8 * (1000 * delta / (1 + elapsed)) + 0.2 * this.velocityX; 143 | this.x += delta; 144 | } 145 | 146 | if (this.settings.verticalScroll) { 147 | var delta = y - this.startY; //Compute move distance 148 | if (delta !== 0) this.dragging = true; 149 | this.startY = y; 150 | this.velocityY = 0.8 * (1000 * delta / (1 + elapsed)) + 0.2 * this.velocityY; 151 | this.y += delta; 152 | } 153 | 154 | this.limitMovement(); 155 | 156 | }; 157 | 158 | /** 159 | * Event triggered when a pointer is released, calculates the automatic scrolling. 160 | */ 161 | ScrollableArea.prototype.endMove = function () { 162 | if (this.startedInside) { 163 | this.pressedDown = false; 164 | this.autoScrollX = false; 165 | this.autoScrollY = false; 166 | 167 | if (!this.settings.kineticMovement) return; 168 | 169 | this.now = Date.now(); 170 | 171 | if(this.game.input.activePointer.withinGame) { 172 | if (this.velocityX > 10 || this.velocityX < -10) { 173 | this.amplitudeX = 0.8 * this.velocityX; 174 | this.targetX = Math.round(this.x + this.amplitudeX); 175 | this.autoScrollX = true; 176 | } 177 | 178 | if (this.velocityY > 10 || this.velocityY < -10) { 179 | this.amplitudeY = 0.8 * this.velocityY; 180 | this.targetY = Math.round(this.y + this.amplitudeY); 181 | this.autoScrollY = true; 182 | } 183 | } 184 | if (!this.game.input.activePointer.withinGame) { 185 | this.velocityWheelXAbs = Math.abs(this.velocityWheelX); 186 | this.velocityWheelYAbs = Math.abs(this.velocityWheelY); 187 | if (this.settings.horizontalScroll && (this.velocityWheelXAbs < 0.1 || !this.game.input.activePointer.withinGame)) { 188 | this.autoScrollX = true; 189 | } 190 | if (this.settings.verticalScroll && (this.velocityWheelYAbs < 0.1 || !this.game.input.activePointer.withinGame)) { 191 | this.autoScrollY = true; 192 | } 193 | } 194 | 195 | if (Math.abs(game.input.x-this.inputX) <= this.settings.clickXThreshold && Math.abs(game.input.y-this.inputY) <= this.settings.clickYThreshold) { 196 | for (var i=0;i 0) { 198 | this.getChildAt(i).events.onInputUp.dispatch(); 199 | } 200 | } 201 | } 202 | } 203 | }; 204 | 205 | ScrollableArea.prototype.scrollTo = function(x, y, time, easing, allowScrollStopOnTouch) { 206 | if (this.scrollTween) { 207 | this.scrollTween.pause(); 208 | } 209 | 210 | x = (x > 0) ? -x : x; 211 | y = (y > 0) ? -y : y; 212 | easing = easing || Phaser.Easing.Quadratic.Out; 213 | time = time || 1000; 214 | allowScrollStopOnTouch = allowScrollStopOnTouch || false; 215 | 216 | this.allowScrollStopOnTouch = allowScrollStopOnTouch; 217 | this.scrollTween = game.add.tween(this); 218 | this.scrollTween.to({x:x, y:y}, time, easing).start(); 219 | } 220 | 221 | /** 222 | * Event called after all the core subsystems and the State have updated, but before the render. 223 | * Create the deceleration effect. 224 | */ 225 | ScrollableArea.prototype.update = function () { 226 | 227 | this.elapsed = Date.now() - this.timestamp; 228 | this.velocityWheelXAbs = Math.abs(this.velocityWheelX); 229 | this.velocityWheelYAbs = Math.abs(this.velocityWheelY); 230 | 231 | if (this.autoScrollX && this.amplitudeX != 0) { 232 | var delta = -this.amplitudeX * Math.exp(-this.elapsed / this.settings.timeConstantScroll); 233 | if (delta > 0.5 || delta < -0.5) { 234 | this.x = this.targetX + delta; 235 | } 236 | else { 237 | this.autoScrollX = false; 238 | //this.x = -this.targetX; 239 | } 240 | } 241 | 242 | if (this.autoScrollY && this.amplitudeY != 0) { 243 | 244 | var delta = -this.amplitudeY * Math.exp(-this.elapsed / this.settings.timeConstantScroll); 245 | if (delta > 0.5 || delta < -0.5) { 246 | this.y = this.targetY + delta; 247 | } 248 | else { 249 | this.autoScrollY = false; 250 | //this.y = -this.targetY; 251 | } 252 | } 253 | 254 | if(!this.autoScrollX && !this.autoScrollY){ 255 | this.dragging = false; 256 | } 257 | 258 | if (this.settings.horizontalWheel && this.velocityWheelXAbs > 0.1) { 259 | this.dragging = true; 260 | this.amplitudeX = 0; 261 | this.autoScrollX = false; 262 | this.x += this.velocityWheelX; 263 | this.velocityWheelX *= 0.95; 264 | } 265 | 266 | if (this.settings.verticalWheel && this.velocityWheelYAbs > 0.1) { 267 | this.dragging = true; 268 | this.autoScrollY = false; 269 | this.y += this.velocityWheelY; 270 | this.velocityWheelY *= 0.95; 271 | } 272 | 273 | this.limitMovement(); 274 | }; 275 | 276 | /** 277 | * Event called when the mousewheel is used, affect the direction of scrolling. 278 | */ 279 | ScrollableArea.prototype.mouseWheel = function (event) { 280 | if (!this.settings.horizontalWheel && !this.settings.verticalWheel) return; 281 | 282 | event.preventDefault(); 283 | 284 | var delta = this.game.input.mouse.wheelDelta * 120 / this.settings.deltaWheel; 285 | 286 | if (this.directionWheel != this.game.input.mouse.wheelDelta) { 287 | this.velocityWheelX = 0; 288 | this.velocityWheelY = 0; 289 | this.directionWheel = this.game.input.mouse.wheelDelta; 290 | } 291 | 292 | if (this.settings.horizontalWheel) { 293 | this.autoScrollX = false; 294 | 295 | this.velocityWheelX += delta; 296 | } 297 | 298 | if (this.settings.verticalWheel) { 299 | this.autoScrollY = false; 300 | 301 | this.velocityWheelY += delta; 302 | } 303 | 304 | }; 305 | 306 | /** 307 | * Stop the Plugin. 308 | * 309 | * @method ScrollableArea#stop 310 | */ 311 | ScrollableArea.prototype.stop = function () { 312 | this.game.input.onDown.remove(this.beginMove, this); 313 | 314 | if (this.callbackID) { 315 | this.game.input.deleteMoveCallback(this.callbackID); 316 | } 317 | else { 318 | this.game.input.deleteMoveCallback(this.moveCanvas, this); 319 | } 320 | 321 | this.game.input.onUp.remove(this.endMove, this); 322 | 323 | this.game.input.mouse.mouseWheelCallback = null; 324 | 325 | }; 326 | 327 | /** 328 | * Reposition the scroller 329 | */ 330 | ScrollableArea.prototype.setPosition = function(position) { 331 | if (position.x) { 332 | this.x += position.x - this._x; 333 | this.maskGraphics.x = this._x = position.x; 334 | } 335 | if (position.y) { 336 | this.y += position.y - this._y; 337 | this.maskGraphics.y = this._y = position.y; 338 | } 339 | } 340 | 341 | /** 342 | * Prevent overscrolling. 343 | */ 344 | ScrollableArea.prototype.limitMovement = function() { 345 | if (this.settings.horizontalScroll) { 346 | if (this.x > this._x) 347 | this.x = this._x; 348 | if (this.x < -(this.width-this._w-this._x)) { 349 | if (this.width > this._w) { 350 | this.x = -(this.width-this._w-this._x); 351 | } 352 | else { 353 | this.x = this._x; 354 | } 355 | } 356 | } 357 | 358 | if (this.settings.verticalScroll) { 359 | if (this.y > this._y) 360 | this.y = this._y; 361 | if (this.y < -(this.height-this._h-this._y)) { 362 | if (this.height > this._h) { 363 | this.y = -(this.height-this._h-this._y); 364 | } 365 | else { 366 | this.y = this._y; 367 | } 368 | } 369 | } 370 | } -------------------------------------------------------------------------------- /dist/phaser-scrollable.js: -------------------------------------------------------------------------------- 1 | var ScrollableArea = function(x, y, w, h, params) { 2 | params = params || {}; 3 | 4 | Phaser.Group.call(this, game); 5 | 6 | this._x = this.x = x; 7 | this._y = this.y = y; 8 | this._w = w; 9 | this._h = h; 10 | 11 | this.maskGraphics = game.add.graphics(x, y); 12 | this.maskGraphics.beginFill(0x000000); 13 | this.maskGraphics.drawRect(0, 0, w, h); 14 | this.maskGraphics.alpha = 0.2; 15 | this.maskGraphics.inputEnabled = true; 16 | this.mask = this.maskGraphics; 17 | 18 | this.dragging = false; 19 | this.pressedDown = false; 20 | this.timestamp = 0; 21 | this.callbackID = 0; 22 | 23 | this.targetX = 0; 24 | this.targetY = 0; 25 | 26 | this.autoScrollX = false; 27 | this.autoScrollY = false; 28 | 29 | this.inputX = 0; 30 | this.inputY = 0; 31 | 32 | this.startX = 0; 33 | this.startY = 0; 34 | 35 | this.velocityX = 0; 36 | this.velocityY = 0; 37 | 38 | this.amplitudeX = 0; 39 | this.amplitudeY = 0; 40 | 41 | this.directionWheel = 0; 42 | 43 | this.velocityWheelX = 0; 44 | this.velocityWheelY = 0; 45 | 46 | this.settings = { 47 | kineticMovement: true, 48 | timeConstantScroll: 325, //really mimic iOS 49 | horizontalScroll: true, 50 | verticalScroll: true, 51 | horizontalWheel: false, 52 | verticalWheel: true, 53 | deltaWheel: 40, 54 | clickXThreshold: 5, 55 | clickYThreshold: 5, 56 | }; 57 | 58 | this.configure(params); 59 | 60 | this.addChild = function(child) { 61 | this.maskGraphics.x = this.parent.x + this._x; 62 | this.maskGraphics.y = this.parent.y + this._y; 63 | ScrollableArea.prototype.addChild.call(this, child); 64 | } 65 | } 66 | 67 | ScrollableArea.prototype = Object.create(Phaser.Group.prototype); 68 | ScrollableArea.prototype.constructor = ScrollableArea; 69 | 70 | /** 71 | * Change Default Settings of the plugin 72 | * 73 | * @method ScrollableArea#configure 74 | * @param {Object} [options] - Object that contain properties to change the behavior of the plugin. 75 | * @param {number} [options.timeConstantScroll=325] - The rate of deceleration for the scrolling. 76 | * @param {boolean} [options.kineticMovement=true] - Enable or Disable the kinematic motion. 77 | * @param {boolean} [options.horizontalScroll=true] - Enable or Disable the horizontal scrolling. 78 | * @param {boolean} [options.verticalScroll=false] - Enable or Disable the vertical scrolling. 79 | * @param {boolean} [options.horizontalWheel=true] - Enable or Disable the horizontal scrolling with mouse wheel. 80 | * @param {boolean} [options.verticalWheel=false] - Enable or Disable the vertical scrolling with mouse wheel. 81 | * @param {number} [options.deltaWheel=40] - Delta increment of the mouse wheel. 82 | */ 83 | ScrollableArea.prototype.configure = function (options) { 84 | if (options) { 85 | for (var property in options) { 86 | if (this.settings.hasOwnProperty(property) && this.settings[property] != null) { 87 | this.settings[property] = options[property]; 88 | } 89 | } 90 | } 91 | }; 92 | 93 | /** 94 | * Start the Plugin. 95 | * 96 | * @method ScrollableArea#start 97 | */ 98 | ScrollableArea.prototype.start = function () { 99 | this.game.input.onDown.add(this.beginMove, this); 100 | this.callbackID = this.game.input.addMoveCallback(this.moveCanvas, this); 101 | this.game.input.onUp.add(this.endMove, this); 102 | this.game.input.mouse.mouseWheelCallback = this.mouseWheel.bind(this); 103 | }; 104 | 105 | /** 106 | * Event triggered when a pointer is pressed down, resets the value of variables. 107 | */ 108 | ScrollableArea.prototype.beginMove = function () { 109 | if (this.allowScrollStopOnTouch && this.scrollTween) { 110 | this.scrollTween.pause(); 111 | } 112 | 113 | if (this.maskGraphics.getBounds().contains(this.game.input.x, this.game.input.y)) { 114 | this.startedInside = true; 115 | 116 | this.startX = this.inputX = this.game.input.x; 117 | this.startY = this.inputY = this.game.input.y; 118 | this.pressedDown = true; 119 | this.timestamp = Date.now(); 120 | this.velocityY = this.amplitudeY = this.velocityX = this.amplitudeX = 0; 121 | } 122 | else { 123 | this.startedInside = false; 124 | } 125 | }; 126 | 127 | /** 128 | * Event triggered when the activePointer receives a DOM move event such as a mousemove or touchmove. 129 | * The camera moves according to the movement of the pointer, calculating the velocity. 130 | */ 131 | ScrollableArea.prototype.moveCanvas = function (pointer, x, y) { 132 | if (!this.pressedDown) return; 133 | 134 | this.now = Date.now(); 135 | var elapsed = this.now - this.timestamp; 136 | this.timestamp = this.now; 137 | 138 | if (this.settings.horizontalScroll) { 139 | var delta = x - this.startX; //Compute move distance 140 | if (delta !== 0) this.dragging = true; 141 | this.startX = x; 142 | this.velocityX = 0.8 * (1000 * delta / (1 + elapsed)) + 0.2 * this.velocityX; 143 | this.x += delta; 144 | } 145 | 146 | if (this.settings.verticalScroll) { 147 | var delta = y - this.startY; //Compute move distance 148 | if (delta !== 0) this.dragging = true; 149 | this.startY = y; 150 | this.velocityY = 0.8 * (1000 * delta / (1 + elapsed)) + 0.2 * this.velocityY; 151 | this.y += delta; 152 | } 153 | 154 | this.limitMovement(); 155 | 156 | }; 157 | 158 | /** 159 | * Event triggered when a pointer is released, calculates the automatic scrolling. 160 | */ 161 | ScrollableArea.prototype.endMove = function () { 162 | if (this.startedInside) { 163 | this.pressedDown = false; 164 | this.autoScrollX = false; 165 | this.autoScrollY = false; 166 | 167 | if (!this.settings.kineticMovement) return; 168 | 169 | this.now = Date.now(); 170 | 171 | if(this.game.input.activePointer.withinGame) { 172 | if (this.velocityX > 10 || this.velocityX < -10) { 173 | this.amplitudeX = 0.8 * this.velocityX; 174 | this.targetX = Math.round(this.x + this.amplitudeX); 175 | this.autoScrollX = true; 176 | } 177 | 178 | if (this.velocityY > 10 || this.velocityY < -10) { 179 | this.amplitudeY = 0.8 * this.velocityY; 180 | this.targetY = Math.round(this.y + this.amplitudeY); 181 | this.autoScrollY = true; 182 | } 183 | } 184 | if (!this.game.input.activePointer.withinGame) { 185 | this.velocityWheelXAbs = Math.abs(this.velocityWheelX); 186 | this.velocityWheelYAbs = Math.abs(this.velocityWheelY); 187 | if (this.settings.horizontalScroll && (this.velocityWheelXAbs < 0.1 || !this.game.input.activePointer.withinGame)) { 188 | this.autoScrollX = true; 189 | } 190 | if (this.settings.verticalScroll && (this.velocityWheelYAbs < 0.1 || !this.game.input.activePointer.withinGame)) { 191 | this.autoScrollY = true; 192 | } 193 | } 194 | 195 | if (Math.abs(game.input.x-this.inputX) <= this.settings.clickXThreshold && Math.abs(game.input.y-this.inputY) <= this.settings.clickYThreshold) { 196 | for (var i=0;i 0) { 198 | this.getChildAt(i).events.onInputUp.dispatch(); 199 | } 200 | } 201 | } 202 | } 203 | }; 204 | 205 | ScrollableArea.prototype.scrollTo = function(x, y, time, easing, allowScrollStopOnTouch) { 206 | if (this.scrollTween) { 207 | this.scrollTween.pause(); 208 | } 209 | 210 | x = (x > 0) ? -x : x; 211 | y = (y > 0) ? -y : y; 212 | easing = easing || Phaser.Easing.Quadratic.Out; 213 | time = time || 1000; 214 | allowScrollStopOnTouch = allowScrollStopOnTouch || false; 215 | 216 | this.allowScrollStopOnTouch = allowScrollStopOnTouch; 217 | this.scrollTween = game.add.tween(this); 218 | this.scrollTween.to({x:x, y:y}, time, easing).start(); 219 | } 220 | 221 | /** 222 | * Event called after all the core subsystems and the State have updated, but before the render. 223 | * Create the deceleration effect. 224 | */ 225 | ScrollableArea.prototype.update = function () { 226 | 227 | this.elapsed = Date.now() - this.timestamp; 228 | this.velocityWheelXAbs = Math.abs(this.velocityWheelX); 229 | this.velocityWheelYAbs = Math.abs(this.velocityWheelY); 230 | 231 | if (this.autoScrollX && this.amplitudeX != 0) { 232 | var delta = -this.amplitudeX * Math.exp(-this.elapsed / this.settings.timeConstantScroll); 233 | if (delta > 0.5 || delta < -0.5) { 234 | this.x = this.targetX + delta; 235 | } 236 | else { 237 | this.autoScrollX = false; 238 | //this.x = -this.targetX; 239 | } 240 | } 241 | 242 | if (this.autoScrollY && this.amplitudeY != 0) { 243 | 244 | var delta = -this.amplitudeY * Math.exp(-this.elapsed / this.settings.timeConstantScroll); 245 | if (delta > 0.5 || delta < -0.5) { 246 | this.y = this.targetY + delta; 247 | } 248 | else { 249 | this.autoScrollY = false; 250 | //this.y = -this.targetY; 251 | } 252 | } 253 | 254 | if(!this.autoScrollX && !this.autoScrollY){ 255 | this.dragging = false; 256 | } 257 | 258 | if (this.settings.horizontalWheel && this.velocityWheelXAbs > 0.1) { 259 | this.dragging = true; 260 | this.amplitudeX = 0; 261 | this.autoScrollX = false; 262 | this.x += this.velocityWheelX; 263 | this.velocityWheelX *= 0.95; 264 | } 265 | 266 | if (this.settings.verticalWheel && this.velocityWheelYAbs > 0.1) { 267 | this.dragging = true; 268 | this.autoScrollY = false; 269 | this.y += this.velocityWheelY; 270 | this.velocityWheelY *= 0.95; 271 | } 272 | 273 | this.limitMovement(); 274 | }; 275 | 276 | /** 277 | * Event called when the mousewheel is used, affect the direction of scrolling. 278 | */ 279 | ScrollableArea.prototype.mouseWheel = function (event) { 280 | if (!this.settings.horizontalWheel && !this.settings.verticalWheel) return; 281 | 282 | event.preventDefault(); 283 | 284 | var delta = this.game.input.mouse.wheelDelta * 120 / this.settings.deltaWheel; 285 | 286 | if (this.directionWheel != this.game.input.mouse.wheelDelta) { 287 | this.velocityWheelX = 0; 288 | this.velocityWheelY = 0; 289 | this.directionWheel = this.game.input.mouse.wheelDelta; 290 | } 291 | 292 | if (this.settings.horizontalWheel) { 293 | this.autoScrollX = false; 294 | 295 | this.velocityWheelX += delta; 296 | } 297 | 298 | if (this.settings.verticalWheel) { 299 | this.autoScrollY = false; 300 | 301 | this.velocityWheelY += delta; 302 | } 303 | 304 | }; 305 | 306 | /** 307 | * Stop the Plugin. 308 | * 309 | * @method ScrollableArea#stop 310 | */ 311 | ScrollableArea.prototype.stop = function () { 312 | this.game.input.onDown.remove(this.beginMove, this); 313 | 314 | if (this.callbackID) { 315 | this.game.input.deleteMoveCallback(this.callbackID); 316 | } 317 | else { 318 | this.game.input.deleteMoveCallback(this.moveCanvas, this); 319 | } 320 | 321 | this.game.input.onUp.remove(this.endMove, this); 322 | 323 | this.game.input.mouse.mouseWheelCallback = null; 324 | 325 | }; 326 | 327 | /** 328 | * Reposition the scroller 329 | */ 330 | ScrollableArea.prototype.setPosition = function(position) { 331 | if (position.x) { 332 | this.x += position.x - this._x; 333 | this.maskGraphics.x = this._x = position.x; 334 | } 335 | if (position.y) { 336 | this.y += position.y - this._y; 337 | this.maskGraphics.y = this._y = position.y; 338 | } 339 | } 340 | 341 | /** 342 | * Prevent overscrolling. 343 | */ 344 | ScrollableArea.prototype.limitMovement = function() { 345 | if (this.settings.horizontalScroll) { 346 | if (this.x > this._x) 347 | this.x = this._x; 348 | if (this.x < -(this.width-this._w-this._x)) { 349 | if (this.width > this._w) { 350 | this.x = -(this.width-this._w-this._x); 351 | } 352 | else { 353 | this.x = this._x; 354 | } 355 | } 356 | } 357 | 358 | if (this.settings.verticalScroll) { 359 | if (this.y > this._y) 360 | this.y = this._y; 361 | if (this.y < -(this.height-this._h-this._y)) { 362 | if (this.height > this._h) { 363 | this.y = -(this.height-this._h-this._y); 364 | } 365 | else { 366 | this.y = this._y; 367 | } 368 | } 369 | } 370 | } --------------------------------------------------------------------------------