After tweeting about the multitouch bug to the author of p5.js, I received the following reply:
github :)
— lauren lee mccarthy (@laurenleemack) November 21, 2016
I therefore filed the bug on the p5.js GitHub.
Wendy forwarded me a video that she had shot of someone using the original version of Reactickles that I am now in the process of porting to the web. I’ve embedded it below:
The three Reactickles that I am aiming to port initially are at the following points in the video:
- 2:41, which I am calling KeyboardScalingCircleGrid.
- 3:57, which I am calling KeyboardBouncingCircleGrid.
- 0:37, which I am calling KeyboardSnake.
So lets start with the development of KeyboardScalingCircleGrid. I began by duplicating the code I wrote yesterday (KeyboardToScreen) and renaming the folder to KeyboardScalingCircleGrid.
I knew that I would have to create an array of ScalingCircles and instantiate them with certain values, so I had a look at the p5.js examples page to see if there were any sketches that might be useful. Sure enough, the Array of Objects example and the Objects 2 example looked perfect.
I started by creating a new ScalingCircle object:
function ScalingCircle(aKey, aCircleRadius){ //ScalingCircle object this.key = aKey; this.circleRadius = aCircleRadius; this.position = createVector(-1,-1); this.position = getCanvasPositionFromKey(aKey); this.colour = color(random(100),50,100); //random hue, saturation 50% and brightness 100% this.display = function(){ var translatedX = this.position.x * windowWidth; var translatedY = this.position.y * windowHeight; fill(this.colour); ellipse(translatedX, translatedY, this.circleRadius, this.circleRadius); }; //don't forget to close your method! }
And creating an array of those objects inside the setup method of the sketch:
var characterSize = 50; var circles = []; //array of ScalingCircle objects var allTheKeys = "1234567890qwertyuiopasdfghjklzxcvbnm"; var circleRadius = 100; function setup() { createCanvas(windowWidth,windowHeight); //make a fullscreen canvas, thanks to: http://codepen.io/grayfuse/pen/wKqLGL textSize(characterSize); colorMode(HSB, 100);// Use HSB with scale of 0-100, see https://p5js.org/reference/#/p5/color for (var i=0; i < allTheKeys.length; i++) { circles.push(new ScalingCircle(allTheKeys[i],circleRadius)); } console.log("The size of the circles array is " + circles.length); }
Finally, I looped through the array of circles inside the draw method of the sketch:
function draw() { background(255); //white background noStroke(); for (var i=0; i<circles.length; i++) { circles[i].display(); } }
This resulted in the following output:
The next step is to add scaling when keyboard buttons are pressed.
I added a scaleUp() method to the ScalingCircle, and changed the display method to ease towards the new targetCircleRadius (The Processing Easing example was useful for this):
function ScalingCircle(aKey, aCircleRadius){ //ScalingCircle object this.key = aKey; this.actualCircleRadius = aCircleRadius; this.targetCircleRadius = aCircleRadius; this.position = createVector(-1,-1); this.position = getCanvasPositionFromKey(aKey); this.colour = color(random(100),50,100); //random hue, saturation 50% and brightness 100% this.display = function(){ var differenceInRadius = this.targetCircleRadius - this.actualCircleRadius; var changeThisFrame = differenceInRadius*easing; this.actualCircleRadius += changeThisFrame; var translatedX = this.position.x * windowWidth; var translatedY = this.position.y * windowHeight; fill(this.colour); ellipse(translatedX, translatedY, this.actualCircleRadius, this.actualCircleRadius); }; //don't forget to close your method! this.scaleUp = function(){ this.targetCircleRadius = this.actualCircleRadius+10; } }
Then added code to check which button was pressed, and to call the scaleUp() method on the correct ScalingCircle in the circles array:
function keyTyped(){ var lowerCaseKey = key.toLowerCase(); //key is a system variable via https://p5js.org/reference/#/p5/key for (var i=0; i<circles.length; i++) { if(lowerCaseKey == circles[i].key){ circles[i].scaleUp(); } } return false; //https://p5js.org/reference/#/p5/keyTyped preventing default behaviour }
This resulted in a pleasing scaling animation, but it wasn’t quite as fast as the video, so I altered the easing ratio to 0.3 from 0.1, as well as setting a fixed number for the target radius of each circle – initially 100 pixels, then 200 pixels on keypress. Finally, I changed the colour of the circle to be 50% transparent to match the blending effect that was visible on the Reactickles 1 demonstration video.
However, in the video I could see that the circles not only scale up, but scale back to their original size – and worse than that they don’t scale up in a linear way, but seem to “bounce” around their full size – overshooting initially and then scaling back. From previous experience in Actionscript and C++ I knew of the existence of Robert Penner’s Easing functions, and that it was very likely that they had already been implemented in p5.js. I Googled “easing functions p5.js” and found p5.ijeoma.js:
A p5.js addon for ijeoma.js, a JS library for creating animations.
I downloaded the library and added it and its dependencies to my libraries folder. Looking at the examples and documentation I started by creating a tween for the scaling up of circles:
function ScalingCircle(aKey){ //ScalingCircle object this.key = aKey; this.startCircleRadius = 100; this.endCircleRadius = 200; this.circleRadius = this.startCircleRadius; //start with the start this.scaleUpDuration = 0.5; //take half of a second to scale up this.scaleDownDelay = this.scaleUpDuration; //wait until the scale up is down to scale down this.scaleDownDuration = 0.25; //take quarter of a second to scale down this.position = createVector(-1,-1); this.position = getCanvasPositionFromKey(aKey); this.colour = color(random(100),50,100,50); //random hue, saturation 50% and brightness 100%, alpha 50% this.display = function(){ var translatedX = this.position.x * windowWidth; var translatedY = this.position.y * windowHeight; fill(this.colour); ellipse(translatedX, translatedY, this.circleRadius, this.circleRadius); }; //don't forget to close your method! this.scaleUpAndThenDown = function(){ //syntax for tweens is createTween(object property, end, duration, [delay], [easing]) //see https://github.com/ekeneijeoma/p5.ijeoma.js var scaleUpTween = createTween('this.circleRadius', this.endCircleRadius, this.scaleUpDuration).easing(Quad.In).play(); var scaleDownTween = createTween('this.circleRadius', this.startCircleRadius, this.scaleDownDuration, this.scaleDownDelay).easing(Quad.Out).play(); } }
Unfortunately, this didn’t work, resulting in the JavaScript errors:
[Log] p5 had problems creating the global function "frames", possibly because your code is already using that name as a variable. You may want to rename your variable to something else. (p5.js, line 9429) [Log] p5 had problems creating the global function "stop", possibly because your code is already using that name as a variable. You may want to rename your variable to something else. (p5.js, line 9429) [Log] The size of the circles array is 36 (sketch.js, line 14) [Warning] Only numbers, p5.colors and p5.vectors are supported. (p5.ijeoma.js, line 142) [Error] TypeError: undefined is not an object (evaluating 'this._properties[i].update') _updateProperties (ijeoma.js:1170) dispatchChangedEvent (ijeoma.js:1297) seek (ijeoma.js:194) play (ijeoma.js:148) scaleUpAndThenDown (sketch.js:59:122) keyTyped (sketch.js:31) _onkeypress (p5.js:16261) (anonymous function)
I duplicated the code into an example, Tweeted at the developer, filed a bug on the library GitHub and rolled back to my previous code.
I decided to try getting elements to scale up and then down, before worrying about “bouncing”.
function ScalingCircle(aKey){ //ScalingCircle object this.key = aKey; this.circleBigRadius = 200; this.circleSmallRadius = 100; this.circleRadius = this.circleSmallRadius; this.position = createVector(-1,-1); this.position = getCanvasPositionFromKey(aKey); this.colour = color(random(100),50,100,50); //random hue, saturation 50% and brightness 100%, alpha 50% this.millisToScaleUp = 50; this.millisToScaleDown = 200; this.startScale = 0; this.endScale = 0; this.scaling = false; this.display = function(){ if(this.scaling){ this.scale(); } var translatedX = this.position.x * windowWidth; var translatedY = this.position.y * windowHeight; fill(this.colour); ellipse(translatedX, translatedY, this.circleRadius, this.circleRadius); }; //don't forget to close your method! this.scale = function(){ var now = millis(); var millisElapsed = now-this.startScale; if(millisElapsed < this.millisToScaleUp){ var howFarAlongScaleUp = millisElapsed/this.millisToScaleUp; this.scaleUp(howFarAlongScaleUp); }else{ var howFarAlongScaleDown = (millisElapsed-this.millisToScaleUp)/this.millisToScaleDown; this.scaleDown(howFarAlongScaleDown); } if(now >= this.endScale){ this.scaling = false; } } this.scaleUp = function(howFarAlongScale){ var differenceInRadius = this.circleBigRadius - this.circleSmallRadius; var newRadius = this.circleSmallRadius+(howFarAlongScale*differenceInRadius); this.circleRadius = newRadius; } this.scaleDown = function(howFarAlongScale){ var differenceInRadius = this.circleBigRadius - this.circleSmallRadius; var newRadius = this.circleBigRadius-(howFarAlongScale*differenceInRadius); this.circleRadius = newRadius; } this.scaleUpandDown = function(){ this.scaling = true; this.startScale = millis(); this.endScale = this.startScale+this.millisToScaleUp+this.millisToScaleDown; } }
This resulted in a scaling up and down, but without the bounce that I could see in the video.