Why this pixelation animation is jolty - javascript

I have the following multi-step pixelation animation. It animates from low-to-high pixelation very slowly to show you that it jolts in between some of the steps, as if the image is slightly moved between renderings. I can't figure out why this is happening or how to make it appear as if the image is staying in one place.
var c = document.createElement('canvas')
c.style.display = 'flex'
c.style.width = '100vw'
c.style.height = '100vh'
c.style['image-rendering'] = 'pixelated'
document.body.appendChild(c)
var x = c.getContext('2d')
x.webkitImageSmoothingEnabled = false
x.mozImageSmoothingEnabled = false
x.msImageSmoothingEnabled = false
x.imageSmoothingEnabled = false
var src = c.getAttribute('data-draw')
var small = `https://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/M101_hires_STScI-PRC2006-10a.jpg/307px-M101_hires_STScI-PRC2006-10a.jpg`
var large = `https://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/M101_hires_STScI-PRC2006-10a.jpg/1280px-M101_hires_STScI-PRC2006-10a.jpg`
// c.width = c.clientWidth
// c.height = c.clientHeight
var stack = []
var start = false
var place = 0
function queue(image, ratio, width, height) {
stack.push({ image, ratio, width, height })
if (start) return
start = true
setTimeout(proceed, 0)
}
function proceed() {
let point = stack.shift()
let w
let h
if (point.ratio) {
w = c.width = c.clientWidth * point.ratio
h = c.height = c.clientHeight * point.ratio
} else {
w = point.width
h = point.height
}
if (!stack.length) {
x.webkitImageSmoothingEnabled = true
x.mozImageSmoothingEnabled = true
x.msImageSmoothingEnabled = true
x.imageSmoothingEnabled = true
c.classList.remove('px')
}
drawImageProp(x, point.image, 0, 0, w, h)
if (stack.length) {
setTimeout(proceed, 1000)
}
}
var s = new Image()
s.onload = function(){
queue(s, 0.01)
queue(s, 0.03)
var i = new Image()
i.onload = function(){
queue(i, 0.03)
queue(i, 0.11)
queue(i, 1)
}
i.src = large
}
s.src = small
function drawImageProp(ctx, img, x, y, w, h, offsetX, offsetY) {
if (arguments.length === 2) {
x = y = 0;
w = ctx.canvas.width;
h = ctx.canvas.height;
}
// default offset is center
offsetX = typeof offsetX === "number" ? offsetX : 0.5;
offsetY = typeof offsetY === "number" ? offsetY : 0.5;
// keep bounds [0.0, 1.0]
if (offsetX < 0) offsetX = 0;
if (offsetY < 0) offsetY = 0;
if (offsetX > 1) offsetX = 1;
if (offsetY > 1) offsetY = 1;
var iw = img.width,
ih = img.height,
r = Math.min(w / iw, h / ih),
nw = iw * r, // new prop. width
nh = ih * r, // new prop. height
cx, cy, cw, ch, ar = 1;
// decide which gap to fill
if (nw < w) ar = w / nw;
if (Math.abs(ar - 1) < 1e-14 && nh < h) ar = h / nh; // updated
nw *= ar;
nh *= ar;
// calc source rectangle
cw = iw / (nw / w);
ch = ih / (nh / h);
cx = (iw - cw) * offsetX;
cy = (ih - ch) * offsetY;
// make sure source rectangle is valid
if (cx < 0) cx = 0;
if (cy < 0) cy = 0;
if (cw > iw) cw = iw;
if (ch > ih) ch = ih;
// fill image in dest. rectangle
ctx.drawImage(img, cx, cy, cw, ch, x, y, w, h);
}
Even in between the last two steps it shifts slightly. Wondering what's going wrong and how to fix it.
FYI, there are two images, a small one and a big one, both the same thing. It first loads the small image at low resolution, then loads the large one. While the large one is loading, it does a few animation steps using the small image to start the animation. Then once the large one is done, it picks up where the small one left off and does a few more animation steps using the large image.

Related

Cursor wont align with HTML Canvas on longer and thinner images

I have a canvas that I am trying to draw on whenever I use square images with dimensions like such:
Width:2925
Height:2354
My cursor works correctly. When I use images with dimensions like Width: 585 Height: 2354 My cursor refuses to align correctly. Here is my JS I am using:
var canvas;
var ctx;
var canvasOffset;
var offsetX;
var offsetY;
var isDrawing = false;
var drag = false;
var rect = {};
var background = new Image();
//canvas.height = background.height;
//canvas.width = background.width;
var aspectRatio;
var parentOffsetWidth;
var parentOffsetHeight;
// Make sure the image is loaded first otherwise nothing will draw.
$(background).on("load", function () {
//ctx.drawImage(background,0,0);
//ctx.width = background.width;
//drawImageProp(ctx, background, 0, 0, background.width, background.height);.
//coverImg(background, 'cover');
//coverImg(background, 'contain');
//ctx.drawImage(background, 0, 0, background.width,background.height,0, 0, ctx.width, ctx.height);
console.log("--background img load--");
parentOffsetWidth = document.getElementById("imgGrid").offsetWidth;
parentOffsetHeight = document.getElementById("imgGrid").offsetHeight;
console.log("w:" + parentOffsetWidth);
console.log("h:" + parentOffsetHeight);
if (parentOffsetWidth < 500) {
//if img is to small to render properly
//parentOffsetWidth = 500;
}
if (background != null) {
canvas.height = (background.height / background.width) * parentOffsetWidth;
canvas.width = parentOffsetWidth;
}
//coverImg(background, 'cover');
drawImageProp(ctx, background, 0, 0, canvas.width, canvas.height, offsetX, offsetY);
})
$(document).ready(function () {
//ctx.drawImage(background,0,0);
})
function Load(backgroundImgSrc) {
console.log("--load--");
console.log(backgroundImgSrc);
background.src = backgroundImgSrc;
aspectRatio = background.height / background.width;
canvas = document.getElementById("canvas");
canvasOffset = $("#canvas").offset();
offsetX = canvasOffset.left;
offsetY = canvasOffset.top;
ctx = canvas.getContext("2d");
init();
}
var startX;
var startY;
const coverImg = (img, type) => {
const imgRatio = img.height / img.width
const winRatio = window.innerHeight / window.innerWidth
if ((imgRatio < winRatio && type === 'contain') || (imgRatio > winRatio && type === 'cover')) {
const h = window.innerWidth * imgRatio
ctx.drawImage(img, 0, (window.innerHeight - h) / 2, window.innerWidth, h)
}
if ((imgRatio > winRatio && type === 'contain') || (imgRatio < winRatio && type === 'cover')) {
const w = window.innerWidth * winRatio / imgRatio
ctx.drawImage(img, (window.w - w) / 2, 0, w, window.innerHeight)
}
}
function drawImageProp(ctx, img, x, y, w, h, offsetX, offsetY) {
if (arguments.length === 2) {
x = y = 0;
w = ctx.canvas.width;
h = ctx.canvas.height;
}
// default offset is center
offsetX = typeof offsetX === "number" ? offsetX : 0.5;
offsetY = typeof offsetY === "number" ? offsetY : 0.5;
// keep bounds [0.0, 1.0]
if (offsetX < 0) offsetX = 0;
if (offsetY < 0) offsetY = 0;
if (offsetX > 1) offsetX = 1;
if (offsetY > 1) offsetY = 1;
var iw = img.width,
ih = img.height,
r = Math.min(w / iw, h / ih),
nw = iw * r, // new prop. width
nh = ih * r, // new prop. height
cx, cy, cw, ch, ar = 1;
// decide which gap to fill
if (nw < w) ar = w / nw;
if (Math.abs(ar - 1) < 1e-14 && nh < h) ar = h / nh; // updated
nw *= ar;
nh *= ar;
// calc source rectangle
cw = iw / (nw / w);
ch = ih / (nh / h);
cx = (iw - cw) * offsetX;
cy = (ih - ch) * offsetY;
// make sure source rectangle is valid
if (cx < 0) cx = 0;
if (cy < 0) cy = 0;
if (cw > iw) cw = iw;
if (ch > ih) ch = ih;
// fill image in dest. rectangle
ctx.drawImage(img, cx, cy, cw, ch, x, y, w, h);
}
////////////////////////////////////
function init() {
canvas.addEventListener('mousedown', mouseDown, false);
canvas.addEventListener('mouseup', mouseUp, false);
canvas.addEventListener('mousemove', mouseMove, false);
}
function mouseDown(e) {
console.log(e);
rect.Left = e.pageX - offsetX;
rect.Top = e.pageY - offsetY;
rect.Left_Percentage = (rect.Left / canvas.width) * 100;
rect.Top_Percentage = (rect.Top / canvas.height) * 100;
drag = true;
}
function mouseUp() {
drag = false;
//ctx.clearRect(0,0,canvas.width,canvas.height);
canvas.style.cursor = "default";
window.dotnetInstance.invokeMethodAsync("ShowPopup", rect);
}
function mouseMove(e) {
if (drag) {
rect.Width = (e.pageX - offsetX) - rect.Left;
rect.Height = (e.pageY - offsetY) - rect.Top;
rect.Height_Percentage = (rect.Height/canvas.height) * 100;
rect.Width_Percentage = (rect.Width / canvas.width) * 100;
ctx.clearRect(0, 0, canvas.width, canvas.height);
draw();
}
}
function draw() {
ctx.setLineDash([10]);
ctx.strokeRect(rect.Left, rect.Top, rect.Width, rect.Height);
}
window.SetDotNetInstance = (dotnetHelper) => {
console.log(dotnetHelper);
if (window.dotnetInstance == null) {
window.dotnetInstance = dotnetHelper;
}
}
function toggleDisplay(current) {
if ($(current).is(":checked")){
$(".clickRegionBlock").hide();
$(".blockNumberInfo").hide();
$("#imgGrid>.border").removeClass("border")
}
else {
$(".clickRegionBlock").show();
$(".blockNumberInfo").show();
$("#imgGrid>.box-border").addClass("border");
}
}
function Zoom() {
var gridSizes = $("#imgGrid").css("grid-template-columns");
var splitArrPx = gridSizes.split(" ");
var firstpx = parseInt(splitArrPx[0].replace("px", ""));
firstpx += 100;
var zoomedGridSizes = "";
splitArrPx.forEach(function (item, index, arr) {
var replacedItem = item.replace(/\d+/, firstpx);
zoomedGridSizes += replacedItem + " ";
})
console.log(zoomedGridSizes);
$("#imgGrid").css("grid-template-columns", zoomedGridSizes);
Load(background.src);
}
And here is my HTML code I am using .Net Blazor as well to achieve this that is why there are variables in my HTML it should not effect my javascript:
<canvas id="canvas" style="background-image:url('api/contentpreview/blocks/#b.PageId/#b.LocalIndex/#Resolution/#zone'); width:100%; height:auto; background-size:cover;" #onload='() => LoadImg("api/contentpreview/blocks/" + b.PageId + "/" + b.LocalIndex + "/" + Resolution + "/" + zone)' />
initCanvas = true;
canvasImg = "api/contentpreview/blocks/" + b.PageId + "/" + b.LocalIndex + "/" + Resolution + "/" + zone;

html canvas draw cylindrical view of the image by drawing vertical slices

I am using below code to draw a cylindrical view of the image by drawing vertical slices of the image:
DrawMug(canvasId, cupImg, rotate, scale) {
var canvas: any = document.getElementById(canvasId);
var ctx = canvas.getContext("2d");
var productImg = new Image();
productImg.onload = () => {
var iw = productImg.width;
var ih = productImg.height;
console.log("height");
canvas.width = iw;
canvas.height = ih;
ctx.drawImage(productImg, 0, 0, productImg.width, productImg.height, 0, 0, iw, ih);
this.loadUpperIMage(ctx, rotate, scale);
};
productImg.src = cupImg;
}
loadUpperIMage(ctx: any, rotate: number, scale: number) {
var img = new Image();
img.src = this.designImgUrl;
img.onload = function() {
var iw = img.width;
var ih = img.height;
var xOffset = 100, //left padding
yOffset = 110; //top padding
var a = 75.0; //image width
var b = 12.0; //round ness
var scaleFactor = iw / (scale * a);
// draw vertical slices (increasing X by 1)
for (var X = 0; X < iw; X += 1) {
var y = b / a * Math.sqrt(a * a - (X - a) * (X - a)); // ellipsis equation
ctx.drawImage(img, (X * scaleFactor), 0, (iw / rotate), ih, X + xOffset, y + yOffset, 1, 174);
}
};
}
It draws the image but the edges doesn't look good as below:
I tried resolving it by decreasing the vertical slice width by replacing the for loop as below
// draw vertical slices (increasing X by 0.1)
for (var X = 0; X < iw; X += 0.1) {
var y = b / a * Math.sqrt(a * a - (X - a) * (X - a)); // ellipsis equation
ctx.drawImage(img, (X * scaleFactor), 0, (iw / rotate), ih, X + xOffset, y + yOffset, 0.1, 174);
}
it fixes the edges but it distorts the image as below, probably because here I am taking destination width as 0.1 but, I am not sure how do I set source width accordingly.
Fiddle
https://jsfiddle.net/au8f6d51/

How to draw a tower make of rectangles going from small to large using raphael graphics using a for loop?

I don't get how to draw a tower using a for loop 8 times?
so the end result will look like this
Here is what I Have
setup = function() {
paper = Raphael ('pyramid', 500,500)
for (i=1; i <= 8; i+=1){
rect = paper.rect(80,i*5,i*15,5)
}
$(document).ready(setup)
Being CH = Canvas Height and CW = Canvas Width,
Each block have Height H = CH/8. For the first, top = 0, bottom = H. For the 2nd, top = H, bottom = H * 2. So for n, top = (n - 1) * H.
The width of the last is CW, decreasing by a variance V each step, so Width W = CW - (8 - n) * V. We can set V = CW/8 for instance. The block is centered, so Left = (CW - W) / 2.
cw = 180;
ch = 180;
s = 8;
paper = Raphael('pyramid', cw, ch)
for (n = 1; n <= s; n += 1) {
h = ch / s;
t = h * (n - 1);
v = cw / s;
w = cw - (s - n) * v;
l = (cw - w) / 2;
paper.rect (l, t, w, h);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.3.0/raphael.min.js"></script>
<div id="pyramid"></div>

How to make blur effect particles in javascript

Hi I want to make a blur effect particle like this:
Can I use shadowBlur and shadowOffsetX/shadowOffsetY to do this? The actual shine will glow and fade a little bit repeatedly, so if I have to write some kind of animation how can I achieve this?
I have tried this code (jsfiddle example) but it doesn't look like the effect. So I wonder how to blur and glow the particle at the same time?
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ra = window.requestAnimationFrame
|| window.webkitRequestAnimationFrame
|| window.mozRequestAnimationFrame
|| window.oRequestAnimationFrame
|| window.msRequestAnimationFrame
|| function(callback) {
window.setTimeout(callback, 1000 / 60);
};
class Particle {
constructor(options) {
this.ctx = options.context;
this.x = options.x;
this.y = options.y;
this.radius = options.radius;
this.lightSize = this.radius;
this.color = options.color;
this.lightDirection = true;
}
glow() {
const lightSpeed = 0.5;
this.lightSize += this.lightDirection ? lightSpeed : -lightSpeed;
if (this.lightSize > this.radius || this.lightSize < this.radius) {
this.lightDirection = !this.lightDirection;
}
}
render() {
this.ctx.clearRect(0, 0, canvas.width, canvas.height);
this.glow();
this.ctx.globalAlpha = 0.5;
this.ctx.fillStyle = this.color;
this.ctx.beginPath();
this.ctx.arc(this.x, this.y, this.lightSize,
0, Math.PI * 2
);
this.ctx.fill();
this.ctx.globalAlpha = 0.62;
this.ctx.beginPath();
this.ctx.arc(this.x, this.y, this.radius * 0.7, 0, Math.PI * 2);
this.ctx.shadowColor = this.color;
this.ctx.shadowBlur = 6;
this.ctx.shadowOffsetX = 0;
this.ctx.shadowOffsetY = 0;
this.ctx.fill();
}
}
var particle = new Particle({
context: ctx,
x: 60,
y: 80,
radius: 12,
color: '#4d88ff'
});
function run() {
particle.render();
ra(run);
}
run();
<canvas id='canvas'></canvas>
There are several ways to do this. For a particle system my option is to pre render the blur using a blur filter. A common filter is the convolution filter. It uses a small array to determine the amount neighboring pixels contribute to each pixel of the image. You are best to look up convolution functions to understand it.
Wiki Convolution and Wiki Gaussian blur for more info.
I am not much of a fan of the standard Gaussian blur or the convolution filter used so in the demo snippet below you can find my version that I think creates a much better blur. The convolution blur filter is procedurally created and is in the imageTools object.
To use create a filter pass an object with properties size the blur amount in pixels and power is the strength. Lower powers is less spread on the blur.
// image must be loaded or created
var blurFilter = imageTools.createBlurConvolutionArray({size:17,power:1}); // size must be greater than 2 and must be odd eg 3,5,7,9...
// apply the convolution filter on the image. The returned image may be a new
//image if the input image does not have a ctx property pointing to a 2d canvas context
image = imageTools.applyConvolutionFilter(image,blurFilter);
In the demo I create a image, draw a circle on it, copy it and pad it so that there is room for the blur. Then create a blur filter and apply it to the image.
When I render the particles I first draw all the unblurred images, then draw the blurred copies with the ctx.globalCompositeOperation = "screen"; so that they have a shine. To vary the amount of shine I use the ctx.globalAlpha to vary the intensity of the rendered blurred image. To improve the FX I have drawn the blur image twice, once with oscillating scale and next at fixed scale and alpha.
The demo is simple, image tools can be found at the top. Then there is some stuff to setup the canvas and handle resize event. Then there is the code that creates the images, and apply the filters. Then starts the render adds some particles and renders everything.
Look in the function drawParticles for how I draw everything.
imageTools has all the image functions you will need. The imageTools.applyConvolutionFilter will apply any filter (sharpen, outline, and many more) you just need to create the appropriate filter. The apply uses the photon count colour model so gives a very high quality result especially for blurs type effects. (though for sharpen you may want to get in and change the squaring of the RGB values, I personally like it other do not)
The blur filter is not fast so if you apply it to larger images It would be best that you break it up in so you do not block the page execution.
A cheap way to get a blur is to copy the image to blur to a smaller version of itself, eg 1/4 then render it scaled back to normal size, the canvas will apply bilinear filtering on the image give a blur effect. Not the best quality but for most situations it is indistinguishable from the more sophisticated blur that I have presented.
UPDATE
Change the code so that the particles have a bit of a 3dFX to show that the blur can work up to larger scales. The blue particles are 32 by 32 image and the blur is 9 pixels with the blur image being 50by 50 pixels.
var imageTools = (function () {
var tools = {
canvas : function (width, height) { // create a blank image (canvas)
var c = document.createElement("canvas");
c.width = width;
c.height = height;
return c;
},
createImage : function (width, height) {
var image = this.canvas(width, height);
image.ctx = image.getContext("2d");
return image;
},
image2Canvas : function (img) {
var image = this.canvas(img.width, img.height);
image.ctx = image.getContext("2d");
image.drawImage(img, 0, 0);
return image;
},
padImage : function(img,amount){
var image = this.canvas(img.width + amount * 2, img.height + amount * 2);
image.ctx = image.getContext("2d");
image.ctx.drawImage(img, amount, amount);
return image;
},
getImageData : function (image) {
return (image.ctx || (this.image2Canvas(image).ctx)).getImageData(0, 0, image.width, image.height);
},
putImageData : function (image, imgData){
(image.ctx || (this.image2Canvas(image).ctx)).putImageData(imgData,0, 0);
return image;
},
createBlurConvolutionArray : function(options){
var i, j, d; // misc vars
var filterArray = []; // the array to create
var size = options.size === undefined ? 3: options.size; // array size
var center = Math.floor(size / 2); // center of array
// the power ? needs descriptive UI options
var power = options.power === undefined ? 1: options.power;
// dist to corner
var maxDist = Math.sqrt(center * center + center * center);
var dist = 0; // distance sum
var sum = 0; // weight sum
var centerWeight; // center calculated weight
var totalDistance; // calculated total distance from center
// first pass get the total distance
for(i = 0; i < size; i++){
for(j = 0; j < size; j++){
d = (maxDist-Math.sqrt((center-i)*(center-i)+(center-j)*(center-j)));
d = Math.pow(d,power)
dist += d;
}
}
totalDistance = dist; // total distance to all points;
// second pass get the total weight of all but center
for(i = 0; i < size; i++){
for(j = 0; j < size; j++){
d = (maxDist-Math.sqrt((center-i)*(center-i)+(center-j)*(center-j)));
d = Math.pow(d,power)
d = d/totalDistance;
sum += d;
}
}
var scale = 1/sum;
sum = 0; // used to check
for(i = 0; i < size; i++){
for(j = 0; j < size; j++){
d = (maxDist-Math.sqrt((center-i)*(center-i)+(center-j)*(center-j)));
d = Math.pow(d,power)
d = d/totalDistance;
filterArray.push(d*scale);
}
}
return filterArray;
},
applyConvolutionFilter : function(image,filter){
imageData = this.getImageData(image);
imageDataResult = this.getImageData(image);
var w = imageData.width;
var h = imageData.height;
var data = imageData.data;
var data1 = imageDataResult.data;
var side = Math.round(Math.sqrt(filter.length));
var halfSide = Math.floor(side/2);
var r,g,b,a,c;
for(var y = 0; y < h; y++){
for(var x = 0; x < w; x++){
var ind = y*4*w+x*4;
r = 0;
g = 0;
b = 0;
a = 0;
for (var cy=0; cy<side; cy++) {
for (var cx=0; cx<side; cx++) {
var scy = y + cy - halfSide;
var scx = x + cx - halfSide;
if (scy >= 0 && scy < h && scx >= 0 && scx < w) {
var srcOff = (scy*w+scx)*4;
var wt = filter[cy*side+cx];
r += data[srcOff+0] * data[srcOff+0] * wt;
g += data[srcOff+1] * data[srcOff+1] * wt;
b += data[srcOff+2] * data[srcOff+2] * wt;
a += data[srcOff+3] * data[srcOff+3] * wt;
}
}
}
data1[ind+0] = Math.sqrt(Math.max(0,r));
data1[ind+1] = Math.sqrt(Math.max(0,g));
data1[ind+2] = Math.sqrt(Math.max(0,b));
data1[ind+3] = Math.sqrt(Math.max(0,a));
}
}
return this.putImageData(image,imageDataResult);
}
};
return tools;
})();
/** SimpleFullCanvasMouse.js begin **/
const CANVAS_ELEMENT_ID = "canv";
const U = undefined;
var w, h, cw, ch; // short cut vars
var canvas, ctx;
var globalTime = 0;
var createCanvas, resizeCanvas, setGlobals;
var L = typeof log === "function" ? log : function(d){ console.log(d); }
createCanvas = function () {
var c,cs;
cs = (c = document.createElement("canvas")).style;
c.id = CANVAS_ELEMENT_ID;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 1000;
document.body.appendChild(c);
return c;
}
resizeCanvas = function () {
if (canvas === U) { canvas = createCanvas(); }
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") { setGlobals(); }
}
setGlobals = function(){
cw = (w = canvas.width) / 2; ch = (h = canvas.height) / 2;
if(particles && particles.length > 0){
particles.length = 0;
}
}
resizeCanvas(); // create and size canvas
window.addEventListener("resize",resizeCanvas); // add resize event
const IMAGE_SIZE = 32;
const IMAGE_SIZE_HALF = 16;
const GRAV = 2001;
const NUM_PARTICLES = 90;
var background = imageTools.createImage(8,8);
var grad = ctx.createLinearGradient(0,0,0,8);
grad.addColorStop(0,"#000");
grad.addColorStop(1,"#048");
background.ctx.fillStyle = grad;
background.ctx.fillRect(0,0,8,8);
var circle = imageTools.createImage(IMAGE_SIZE,IMAGE_SIZE);
circle.ctx.fillStyle = "#5BF";
circle.ctx.arc(IMAGE_SIZE_HALF, IMAGE_SIZE_HALF, IMAGE_SIZE_HALF -2,0, Math.PI * 2);
circle.ctx.fill();
var blurFilter = imageTools.createBlurConvolutionArray({size:9,power:1}); // size must be greater than 2 and must be odd eg 3,5,7,9...
var blurCircle = imageTools.padImage(circle,9);
blurCircle = imageTools.applyConvolutionFilter(blurCircle,blurFilter)
var sun = imageTools.createImage(64,64);
grad = ctx.createRadialGradient(32,32,0,32,32,32);
grad.addColorStop(0,"#FF0");
grad.addColorStop(1,"#A40");
sun.ctx.fillStyle = grad;
sun.ctx.arc(32,32,32 -2,0, Math.PI * 2);
sun.ctx.fill();
var sunBlur = imageTools.padImage(sun,17);
blurFilter = imageTools.createBlurConvolutionArray({size:17,power:1}); // size must be greater than 2 and must be odd eg 3,5,7,9...
sunBlur = imageTools.applyConvolutionFilter(sunBlur,blurFilter);
var particles = [];
var createParticle = function(x,y,dx,dy){
var dir = Math.atan2(y-ch,x-cw);
var dist = Math.sqrt(Math.pow(y-ch,2)+Math.pow(x-cw,2));
var v = Math.sqrt(GRAV / dist); // get apporox orbital speed
return {
x : x,
y : y,
dx : dx + Math.cos(dir + Math.PI/2) * v, // set orbit speed at tangent
dy : dy + Math.sin(dir + Math.PI/2) * v,
s : (Math.random() + Math.random() + Math.random())/4 + 0.5, // scale
v : (Math.random() + Math.random() + Math.random()) / 3 + 2, // glow vary rate
};
}
var depthSort = function(a,b){
return b.y - a.y;
}
var updateParticles = function(){
var i,p,f,dist,dir;
for(i = 0; i < particles.length; i ++){
p = particles[i];
dist = Math.sqrt(Math.pow(cw-p.x,2)+Math.pow(ch-p.y,2));
dir = Math.atan2(ch-p.y,cw-p.x);
f = GRAV * 1 / (dist * dist);
p.dx += Math.cos(dir) * f;
p.dy += Math.sin(dir) * f;
p.x += p.dx;
p.y += p.dy;
p.rx = ((p.x - cw ) / (p.y + h)) * h + cw;
p.ry = ((p.y - ch ) / (p.y + h)) * h * -0.051+ ch;
//p.ry = ((h-p.y) - ch) * 0.1 + ch;
p.rs = (p.s / (p.y + h)) * h
}
particles.sort(depthSort)
}
var drawParticles = function(){
var i,j,p,f,dist,dir;
// draw behind the sun
for(i = 0; i < particles.length; i ++){
p = particles[i];
if(p.y - ch < 0){
break;
}
ctx.setTransform(p.rs,0,0,p.rs,p.rx,p.ry);
ctx.drawImage(circle,-IMAGE_SIZE_HALF,-IMAGE_SIZE_HALF);
}
// draw glow for behind the sun
ctx.globalCompositeOperation = "screen";
var iw = -blurCircle.width/2;
for(j = 0; j < i; j ++){
p = particles[j];
ctx.globalAlpha = ((Math.sin(globalTime / (50 * p.v)) + 1) / 2) * 0.6 + 0.4;
var scale = (1-(Math.sin(globalTime / (50 * p.v)) + 1) / 2) * 0.6 + 0.6;
ctx.setTransform(p.rs * 1.5 * scale,0,0,p.rs * 1.5* scale,p.rx,p.ry);
ctx.drawImage(blurCircle,iw,iw);
// second pass to intensify the glow
ctx.globalAlpha = 0.7;
ctx.setTransform(p.rs * 1.1,0,0,p.rs * 1.1,p.rx,p.ry);
ctx.drawImage(blurCircle,iw,iw);
}
// draw the sun
ctx.globalCompositeOperation = "source-over";
ctx.globalAlpha = 1;
ctx.setTransform(1,0,0,1,cw,ch);
ctx.drawImage(sun,-sun.width/2,-sun.height/2);
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "screen";
ctx.setTransform(1,0,0,1,cw,ch);
ctx.drawImage(sunBlur,-sunBlur.width/2,-sunBlur.height/2);
var scale = Math.sin(globalTime / 100) *0.5 + 1;
ctx.globalAlpha = (Math.cos(globalTime / 100) + 1) * 0.2 + 0.4;;
ctx.setTransform(1 + scale,0,0,1 + scale,cw,ch);
ctx.drawImage(sunBlur,-sunBlur.width/2,-sunBlur.height/2);
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "source-over";
// draw in front the sun
for(j = i; j < particles.length; j ++){
p = particles[j];
if(p.y > -h){ // don't draw past the near view plane
ctx.setTransform(p.rs,0,0,p.rs,p.rx,p.ry);
ctx.drawImage(circle,-IMAGE_SIZE_HALF,-IMAGE_SIZE_HALF);
}
}
ctx.globalCompositeOperation = "screen";
var iw = -blurCircle.width/2;
for(j = i; j < particles.length; j ++){
p = particles[j];
if(p.y > -h){ // don't draw past the near view plane
ctx.globalAlpha = ((Math.sin(globalTime / (50 * p.v)) + 1) / 2) * 0.6 + 0.4;
var scale = (1-(Math.sin(globalTime / (50 * p.v)) + 1) / 2) * 0.6 + 0.6;
ctx.setTransform(p.rs * 1.5 * scale,0,0,p.rs * 1.5* scale,p.rx,p.ry);
ctx.drawImage(blurCircle,iw,iw);
// second pass to intensify the glow
ctx.globalAlpha = 0.7;
ctx.setTransform(p.rs * 1.1,0,0,p.rs * 1.1,p.rx,p.ry);
ctx.drawImage(blurCircle,iw,iw);
}
}
ctx.globalCompositeOperation = "source-over";
}
var addParticles = function(count){
var ww = (h-10)* 2;
var cx = cw - ww/2;
var cy = ch - ww/2;
for(var i = 0; i < count; i ++){
particles.push(createParticle(cx + Math.random() * ww,cy + Math.random() * ww, Math.random() - 0.5, Math.random() - 0.5));
}
}
function display(){ // put code in here
if(particles.length === 0){
addParticles(NUM_PARTICLES);
}
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.drawImage(background,0,0,w,h)
updateParticles();
drawParticles();
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "source-over";
}
function update(timer){ // Main update loop
globalTime = timer;
display(); // call demo code
requestAnimationFrame(update);
}
requestAnimationFrame(update);
/** SimpleFullCanvasMouse.js end **/

HTML5 canvas draw a rectangle with gelatine effect

I'm creating a platformer game in javascript in which players are rectangles and they can jump on and off platforms. This is pretty straightforward but now I'm trying to add a 'gelatine effect' to the players so that when they land on a platform they move a bit(like a gelatine). I've been searching for quite some time now but I can't seem to find any good examples on how I can change the shape of a rectangle.
All I've come up with is by using #keyframes in css and i've tried implementing it, which works if I use it on html elements. This is because with the DOM elements I can access the CSSStyleDeclaration with .style but if I create a new player object I can't(as seen in my code below).
I'm not sure how I can convert the css #keyframes to javascript or if what i'm doing isn't possible.. or if there perhaps are other (better) ways to achieve my goal?
var keystate = [];
var players = [];
var jelly = document.getElementById('jelly');
console.log(jelly.style); // shows the CSSStyleDeclarations
document.body.addEventListener("keydown", function(e) {
keystate[e.keyCode] = true;
});
document.body.addEventListener("keyup", function(e) {
keystate[e.keyCode] = false;
});
function gelatine(e) {
if (e.style.webkitAnimationName !== 'gelatine') {
e.style.webkitAnimationName = 'gelatine';
e.style.webkitAnimationDuration = '0.5s';
e.style.display = 'inline-block';
setTimeout(function() {
e.style.webkitAnimationName = '';
}, 1000);
}
}
var canvas = document.getElementById("canvas");
var context = canvas.getContext('2d');
canvas.width = 500;
canvas.height = 300;
function Player(x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.jumping = false;
this.velocityY = 0;
this.gravity = 0.3;
this.speed = 5;
this.timer = 0;
this.delay = 120;
}
Player.prototype.render = function render() {
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = 'blue';
context.fillRect(this.x, this.y, this.width, this.height);
context.fillStyle = 'black';
context.font = '20pt sans-serif';
context.fillText("I'm not jelly:(", this.x - 160, this.y + 30);
};
Player.prototype.update = function update() {
// arrow up key to jump with the player
if (keystate[38]) {
if (!this.jumping) {
this.jumping = true;
this.velocityY = -this.speed * 2;
}
}
this.velocityY += this.gravity;
this.y += this.velocityY;
if (this.y >= canvas.height - this.height) {
this.y = canvas.height - this.height;
this.jumping = false;
}
if (this.timer === 0) {
gelatine(jelly);
this.timer = this.delay;
}
if (this.timer > 0 && this.timer <= this.delay) {
this.timer--;
}
};
players.push(new Player((canvas.width / 2) - 25, (canvas.height / 2), 50, 50));
console.log(players[0].style); // no CSSStyleDeclarations :(
function render() {
for (var i = 0; i < players.length; i++) {
players[i].render();
}
}
function update() {
for (var i = 0; i < players.length; i++) {
players[i].update();
}
}
function tick() {
update();
render();
requestAnimationFrame(tick);
}
tick();
<html>
<head>
<style>
#jelly {
position: absolute;
margin-left: auto;
margin-right: auto;
top: 80px;
bottom: 0;
left: 0;
right: 0;
display: inline-block;
width: 100px;
height: 100px;
background: blue;
}
p {
font-size: 20px;
color: white;
}
canvas {
border: 1px solid #000;
position: absolute;
margin: auto;
top: 20px;
bottom: 0;
left: 0;
right: 0;
}
#keyframes gelatine {
25% {
-webkit-transform: scale(0.9, 1.1);
transform: scale(0.9, 1.1);
}
50% {
-webkit-transform: scale(1.1, 0.9);
transform: scale(1.1, 0.9);
}
75% {
-webkit-transform: scale(0.95, 1.05);
transform: scale(0.95, 1.05);
}
}
</style>
<title>Jelly rectangle</title>
</head>
<body>
<div id="jelly">
<p>&nbsp&nbsp i'm jelly :)</p>
</div>
<canvas id='canvas'></canvas>
</body>
</html>
Jelly.
To create a jelly effect for using in a game we can take advantage of a step based effect that is open ended (ie the effect length is dependent on the environment and has no fixed time)
Jelly and most liquids have a property that they are incompressible. This means that no matter the force applied to them the volume does not change. For a 2D game this mean that for jelly the area does not change. This means we can squash the height and knowing the area calculate the width.
Thus when a object drops and hits the ground we can apply a squashing force to the object in the height direction. Simulating a dampened spring we can produce a very realistic jelly effect.
Defining the jelly
I am being a little silly as it is the season so the variables wobbla and bouncy define the dampened spring with wobbla being the stiffness of the spring from 0 to 1, with 0.1 being very soft to 1 being very stiff. Bouncy being the damping of the spring from 0 to 1, with 0 having 100% damping and 1 having no damping.
I have also added react which is a simple multiplier to the force applied to the spring. As this is a game there is a need to exaggerate effects. react multiplies the force applied to the spring. Values under 1 reduce the effect, values over 1 increase the effect.
hr and hd (yes bad names) are the real height (displayed) and the height delta ( the change in height per frame). These two variable control the spring effect.
There is a flag to indicate that the jelly is on the ground and the sticky flag if true keeps the jelly stuck to the ground.
There are also three functions to control the jelly
function createJellyBox(image,x, y, wobbla, bouncy, react){
return {
img:image,
x : x, // position
y : y,
dx : 0, // movement deltas
dy : 0,
w : image.width, // width
h : image.height, // height
area : image.width * image.height, // area
hr : image.height, // squashed height chaser
hd : 0, // squashed height delta
wobbla : wobbla, // higher values make it wobble more
bouncy : bouncy, // higher values make it react to change in speed
react : react, // additional reaction multiplier. < 1 reduces reaction > 1 increases reaction
onGround : false, // true if on the ground
sticky : true, // true to stick to the ground. false to let it bounce
squashed : 1, // the amount of squashing or stretching along the height
force : 0, // the force applied to the jelly when it hits the ground
draw : drawJelly, // draw function
update : updateJelly, // update function
reset : resetJelly, // reset function
}
}
The functions
There are three functions to control the jelly, reset, update and draw. Sorry the answer is over the 30000 character limit so find the functions in the demo below.
Draw
Draw simply draws the jelly. It uses the squashed value to calculate the height and from that calculates the width, then simply draws the image with the set width and height. The image is drawn at its center to keep calculations simpler.
Reset
Simply resets the jelly at a position defined by x and y you can modify it to remove any wobbles by setting jelly.hr = jelly.h and jelly.hd = 0
Update
This is where the hard work is. It updates the object position by adding gravity to the delta Y. It checks if the jelly has hit the ground, if it has it applies the force to the jelly.hr spring by adding to the jelly.hd (height delta) the force equal to the speed of the inpact times jelly.react
I found that the force applied should be over time so there is a little cludge (marked with comments) to apply a squashing force over time. That can be removed but it just reduces the smoothness of the jelly wobbly effect.
Last thing is to recalculate the height and move the jelly so that it does not cross into the ground.
Clear as mud cake I am sure. So feel free anyone to ask questions if there is a need to clarify.
DEMO
What would any good SO answer be without a demo. So here is a jelly simulation. Click on the canvas to drop the jelly. The three sliders on the top left control the values Wobbla, Bouncy, and React. Play witht the values to change the jelly effect. The check box turn on and off sticky, but you need a long screen to get the impact you need to bounce up. Remove the cludge code to see sticky real purpose.
As I needed a UI there is a lot of extra code that does not apply to the answer. The Answer code is clearly marked so to be easy to find. The main loop is at the bottom. I have not yet tested it on FF but it should work. If not let me know and I will fix it.
var STOP = false; // stops the app
var jellyApp = function(){
/** Compiled by GROOVER Quick run 9:43pm DEC-22-2015 **/
/** fullScreenCanvas.js begin **/
var canvas = (function(){
var canvas = document.getElementById("canv");
if(canvas !== null){
document.body.removeChild(canvas);
}
// creates a blank image with 2d context
canvas = document.createElement("canvas");
canvas.id = "canv";
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.position = "absolute";
canvas.style.top = "0px";
canvas.style.left = "0px";
canvas.style.zIndex = 1000;
canvas.ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
return canvas;
})();
var ctx = canvas.ctx;
/** fullScreenCanvas.js end **/
/** MouseFull.js begin **/
var canvasMouseCallBack = undefined; // if needed
var mouse = (function(){
var mouse = {
x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false,
interfaceId : 0, buttonLastRaw : 0, buttonRaw : 0,
over : false, // mouse is over the element
bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
getInterfaceId : function () { return this.interfaceId++; }, // For UI functions
startMouse:undefined,
};
function mouseMove(e) {
var t = e.type, m = mouse;
m.x = e.offsetX; m.y = e.offsetY;
if (m.x === undefined) { m.x = e.clientX; m.y = e.clientY; }
m.alt = e.altKey;m.shift = e.shiftKey;m.ctrl = e.ctrlKey;
if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1];
} else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2];
} else if (t === "mouseout") { m.buttonRaw = 0; m.over = false;
} else if (t === "mouseover") { m.over = true;
} else if (t === "mousewheel") { m.w = e.wheelDelta;
} else if (t === "DOMMouseScroll") { m.w = -e.detail;}
if (canvasMouseCallBack) { canvasMouseCallBack(m.x, m.y); }
e.preventDefault();
}
function startMouse(element){
if(element === undefined){
element = document;
}
"mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",").forEach(
function(n){element.addEventListener(n, mouseMove);});
}
mouse.mouseStart = startMouse;
return mouse;
})();
if(typeof canvas === "undefined"){
mouse.mouseStart(canvas);
}else{
mouse.mouseStart();
}
/** MouseFull.js end **/
// unit size for rendering scale
var ep = canvas.height / 72;
// font constants
const FONT = "px Arial Black";
const FONT_SIZE = Math.ceil(3 * ep);
const GRAVITY = ep * (1/5);
const GROUND_AT = canvas.height - canvas.height * (1/20);
// Answer code.
//----------------------------------------------------------------------- // draw the jelly
function drawJelly(ctx){
var w,h;
w = this.w,
h = this.h;
h *= this.squashed;
// the width is ajusted so that the area of the rectagle remains constant
w = this.area / h; // to keep the area constant
ctx.drawImage(this.img,this.x - w / 2, this.y - h / 2, w, h);
}
function resetJelly(x,y){ // reset the jelly position
this.x = x;
this.y = y;
this.onGround = false;
this.dx = 0;
this.dy = 0;
}
// do the jelly math
function updateJelly(){
var h; // temp height
var hitG = false; // flag that the ground has just been hit
h = this.h * this.squashed; // get the height from squashed
if(!this.onGround){ // if not on the ground add grav
this.dy += GRAVITY;
}else{ // if on the ground move it so it touches correctly
this.dy = 0;
this.y = GROUND_AT - h / 2;
}
// update the position
this.x += this.dx;
this.y += this.dy;
// check if it has hit the ground
if(this.y + h / 2 >= GROUND_AT && this.dy >= 0){
this.hd += this.dy * this.react; // add the hit speed to the height delta
// multiply with react to inhance or reduce effect
this.force += this.dy * this.react;
hitG = true;
this.onGround = true; // flag that jelly is on the ground
}
if(this.force > 0){
this.hd += this.force;
this.force *= this.wobbla;
}
this.hd += (this.h - this.hr) * this.wobbla; // add wobbla to delta height
this.hd *= this.bouncy; // reduce bounce
this.hr += this.hd; // set the real height
this.squashed = this.h / this.hr; // calculate the new squashed amount
h = this.h * this.squashed; // recalculate hieght to make sure
// the jelly does not overlap the ground
// do the finnal position ajustment to avoid overlapping the ground
if(this.y + h / 2 >= GROUND_AT || hitG || (this.sticky && this.onGround)){
this.y = GROUND_AT - h / 2;
}
}
// create a jelly box
function createJellyBox(image,x, y, wobbla, bouncy, react){
return {
img:image,
x : x, // position
y : y,
dx : 0, // movement deltas
dy : 0,
w : image.width, // width
h : image.height, // height
area : image.width * image.height, // area
hr : image.height, // squashed height chaser
hd : 0, // squashed height delta
wobbla : wobbla, // higher values make it wobble more
bouncy : bouncy, // higher values make it react to change in speed
react : react, // additional reaction multiplier. < 1 reduces reaction > 1 increases reaction
onGround : false, // true if on the ground
sticky : true, // true to stick to the groun. false to let it bounce
squashed : 1, // the amount of squashing or streaching along the height
force : 0,
draw : drawJelly, // draw function
update : updateJelly, // update function
reset : resetJelly, // reset function
}
}
// --------------------------------------------------------------------------------------------
// END OF ANSWER CODE.
// FIND the usage at the bottom inside the main loop function.
// --------------------------------------------------------------------------------------------
// The following code is just helpers and UI stuff and are not related to the answer.
var createImage = function (w, h ){
var image = document.createElement("canvas");
image.width = w;
image.height = h;
image.ctx = image.getContext("2d");
return image;
}
var drawSky = function (img, col1, col2){
var c, w, h;
c = img.ctx;
w = img.width;
h = img.height;
var g = c.createRadialGradient(w * (1 / 2), h * (3 / 2), h * (1 / 2) +w * (1 / 4), w * (1 / 2), h * (3 / 2), h * (3 / 2) + w * ( 1 / 4));
g.addColorStop(0,col1);
g.addColorStop(1,col2);
c.fillStyle = g;
c.fillRect(0, 0, w, h);
return img;
}
var drawGround = function (img, col1, col2,lineColour, lineWidth) {
var c, w, h, lw;
c = img.ctx;
w = img.width;
h = img.height;
lw = lineWidth;
var g = c.createLinearGradient(0, 0, 0, h + lineWidth);
g.addColorStop(0, col1);
g.addColorStop(1, col2);
c.fillStyle = g;
c.lineWidth = lw;
c.strokeStyle = lineColour;
c.strokeRect(-lw * 2, lw / 2, w + lw * 4, h + lw * 4);
c.fillRect(-lw * 2, lw / 2, w + lw * 4, h + lw * 4);
return img;
}
/** CanvasUI.js begin **/
var drawRoundedBox = function (img, colour, rounding, lineColour, lineWidth) {
var c, x, y, w, h, r, p
p = Math.PI/2; // 90 deg
c = img.ctx;
w = img.width;
h = img.height;
lw = lineWidth;
r = rounding ;
c.lineWidth = lineWidth;
c.fillStyle = colour;
c.strokeStyle = lineColour;
c.beginPath();
c.arc(w - r - lw / 2, h - r - lw / 2, r, 0, p);
c.lineTo(r + lw / 2, h - lw / 2);
c.arc(r + lw / 2, h - r - lw / 2, r, p, p * 2);
c.lineTo(lw / 2, h - r - lw / 2);
c.arc(r + lw / 2, r + lw / 2, r, p * 2, p * 3);
c.lineTo(w-r - lw / 2, lw / 2);
c.arc(w - r - lw / 2, r + lw / 2, r, p * 3, p * 4);
c.closePath();
c.stroke();
c.fill();
return img;
}
var drawTick = function (img , col, lineColour, lineWidth){
var c, w, h, lw, m, l;
m = function (x, y) {c.moveTo(lw / 2 + w * x, lw / 2 + h * y);};
l = function (x, y) {c.lineTo(lw / 2 + w * x, lw / 2 + h * y);};
lw = lineWidth;
c = img.ctx;
w = img.width - lw;
h = img.height - lw;
c.fillStyle = col;
c.strokeStyle = lineColour;
c.lineWidth = lw;
c.beginPath();
m(1, 0);
l(5 / 8, 1);
l(0, 3 / 4);
l(1 / 4, 2 / 4);
l(2 / 4, 3 / 4);
l(1, 0);
c.stroke();
c.fill();
return img;
}
var setFont = function(ctx,font,align){
ctx.font = font;
ctx.textAlign = align;
}
var measureText = function(ctx,text){
return ctx.measureText(text).width;
}
var drawText = function(ctx,text,x,y,col,col1){
var of;
of = Math.floor(FONT_SIZE/10);
ctx.fillStyle = col1;
ctx.fillText(text,x+of,y+of);
ctx.fillStyle = col;
ctx.fillText(text,x,y);
}
var drawSlider = function(ctx){
var x,y;
x = this.owner.x;
y = this.owner.y;
ctx.drawImage(this.image, this.x + x, this.y + y);
ctx.drawImage(this.nob, this.nx + x, this.ny + y);
}
var updateSlider = function(mouse){
var mx, my;
mx = mouse.x - this.owner.x;
my = mouse.y - this.owner.y;
this.cursor = "";
if(this.owner.dragging === -1 || this.owner.dragging === this.id ){
if(mx >= this.x && mx < this.x + this.w &&
my >= this.y && my <= this.y + this.h){
this.mouseOver = true;
this.cursor = "pointer"
}else{
this.mouseOver = false;
}
if(mx >= this.nx && mx < this.nx + this.nw &&
my >= this.ny && my <= this.ny + this.nh){
this.mouseOverNob = true;
this.cursor = "ew-resize"
}else{
this.mouseOverNob = false;
}
if((mouse.buttonRaw&1) === 1 && (this.mouseOver||this.mouseOverNob) && !this.dragging){
this.owner.dragging = this.id;
this.dragging = true;
this.cursor = "ew-resize"
}else
if(this.dragging){
this.cursor = "ew-resize"
if((mouse.buttonRaw & 1)=== 0){
this.dragging = false;
this.owner.dragging = -1;
this.cursor = "pointer";
}
var p = mx- (this.x+this.nw/2);
p /= (this.w-this.nw);
p *= this.range;
p += this.min;
this.value = Math.min(this.max, Math.max(this.min, p));
}
if(this.mouseOver || this.mouseOverNob || this.dragging){
this.owner.toolTip = this.toolTip.replace("##",this.value.toFixed(this.decimals));
}
}
this.nx = (this.value - this.min) / this.range * (this.w - this.nw) + this.x;
}
var createSlider = function(image,nobImage, x, y, value, min, max, toolTip) {
var decimals = 0;
if(toolTip.indexOf("#.") > -1){
if(toolTip.indexOf(".DDD") > -1){
decimals = 3;
toolTip = toolTip.replace("#.DDD","##");
}else
if(toolTip.indexOf(".DD") > -1){
decimals = 2;
toolTip = toolTip.replace("#.DD","##");
}else
if(toolTip.indexOf(".D") > -1){
decimals = 1;
toolTip = toolTip.replace("#.D","##");
}else{
toolTip = toolTip.replace("#.","##");
}
}
return {
id : undefined,
image : image,
nob : nobImage,
min : min,
max : max,
x : x,
y : y + (nobImage.height - image.height)/2,
ny : y ,
nx : ((value - min) / (max - min)) * (image.width - nobImage.width) + x,
range : max - min,
w : image.width,
h : image.height,
nw : nobImage.width,
nh : nobImage.height,
value : value,
maxH : Math.max( image.height, nobImage.height),
mouseOver : false,
mouseOverNob : false,
toolTip:toolTip,
decimals:decimals,
dragging : false,
update : updateSlider,
draw : drawSlider,
position : function (x, y){
this.x += x;
this.y += y;
this.nx += x;
this.ny += y;
},
}
}
var drawTickCont = function(ctx){
var x,y, ofx, ofy;
x = this.owner.x;
y = this.owner.y;
ctx.drawImage(this.image, this.x + x, this.y + y);
ofy = this.h / 2 - this.textImage.height / 2;
ofx = this.w / 2;
ctx.drawImage(this.textImage, this.x + x + this.w + ofx, this.y + y + ofy);
if(this.value){
x -= this.tickImage.width * ( 1/ 4);
y -= this.tickImage.height * ( 2/ 5);
ctx.drawImage(this.tickImage, this.x + x, this.y + y);
}
}
var updateTick = function(mouse){
var mx, my;
mx = mouse.x - this.owner.x;
my = mouse.y - this.owner.y;
this.cursor = "";
if(this.owner.dragging === -1 || this.owner.dragging === this.id ){
if(mx >= this.x && mx < this.x + this.w &&
my >= this.y && my <= this.y + this.h){
this.mouseOver = true;
this.cursor = "pointer"
}else{
this.mouseOver = false;
}
if((mouse.buttonRaw&1) === 1 && this.mouseOver && !this.dragging){
this.owner.dragging = this.id;
this.dragging = true;
}else
if(this.dragging){
if((mouse.buttonRaw & 1)=== 0){
if(this.mouseOver){
this.value = ! this.value;
}
this.dragging = false;
this.owner.dragging = -1;
this.cursor = "pointer";
}
}
if(this.mouseOver || this.dragging){
this.owner.toolTip = this.toolTip;
}
}
}
var createTick= function(image,tickImage,textImage, x, y, value, toolTip) {
return {
id : undefined,
image : image,
tickImage : tickImage,
textImage : textImage,
x : x,
y : y,
w : image.width,
h : image.height,
value : value,
maxH : Math.max( image.height, tickImage.height),
mouseOver : false,
mouseOverNob : false,
toolTip:toolTip,
dragging : false,
update : updateTick,
draw : drawTickCont,
position : function (x, y){
this.x += x;
this.y += y;
},
}
}
function UI(ctx, mouse, x, y){
this.dragging = -1;
var ids = 0;
var controls = [];
var length = 0;
this.x = x;
this.y = y;
var posX = 0;
var posY = 0;
this.addControl = function (control, name) {
control.id = ids ++;
control.owner = this;
control.position(posX, posY);
posY += control.maxH + ep;
length = controls.push(control)
this[name] = control;
}
this.update = function(){
var i, cursor, c;
cursor = "";
this.toolTip = "";
for(i = 0; i < length; i ++){
c = controls[i];
c.update(mouse);
if(c.cursor !== ""){
cursor = c.cursor;
}
c.draw(ctx);
}
if(cursor === ""){
ctx.canvas.style.cursor = "default";
}else{
ctx.canvas.style.cursor = cursor;
}
if(this.toolTip !== ""){
if(mouse.y - FONT_SIZE * (5 / 3) < 0){
drawText(ctx, this.toolTip, mouse.x, mouse.y + FONT_SIZE * (4 / 3), "#FD4", "#000");
}else{
drawText(ctx, this.toolTip, mouse.x, mouse.y - FONT_SIZE * (2 / 3), "#FD4", "#000");
}
}
}
}
/** CanvasUI.js end **/
//-----------------------------
// create UI
//-----------------------------
setFont(ctx, FONT_SIZE + FONT, "left");
// images for UI
var tickBox = drawRoundedBox(createImage(4 * ep, 4 * ep), "#666", (3 / 2) * ep, "#000", (1 / 2) * ep);
var tickBoxTick = drawTick(createImage(6 * ep, 6 * ep), "#0D0", "#000", (1 / 2) * ep);
var w = measureText(ctx, "Sticky");
var tickBoxText = createImage(w, FONT_SIZE);
setFont(tickBoxText.ctx, FONT_SIZE + FONT, "left");
drawText(tickBoxText.ctx, "Sticky", 0, FONT_SIZE * (3 / 4), "white", "black");
var sliderBar = drawRoundedBox(createImage(20 * ep, 2 * ep), "#666", ep * 0.9, "#000", (1 / 2) * ep);
var sliderNob = drawRoundedBox(createImage(3 * ep, 4 * ep), "#AAA", ep, "#000", (1 / 2) * ep);
// UI control
var controls = new UI(ctx, mouse, 10, 10);
controls.addControl(createSlider(sliderBar, sliderNob, 0, 0, 0.3, 0, 1, "Wobbla #.DD"), "wobbla");
controls.addControl(createSlider(sliderBar, sliderNob, 0, 0, 0.8, 0, 1, "Bouncy #.DD"), "bouncy");
controls.addControl(createSlider(sliderBar, sliderNob, 0, 0, 1.5, 0, 2, "React #.DD"), "react");
controls.addControl(createTick(tickBox, tickBoxTick, tickBoxText, 0, 0, true, "Activate / Deactivate sticky option."), "sticky");
//-----------------------------
// create playfield
//-----------------------------
var skyImage = drawSky(createImage(32, 32), "#9EF", "#48D");
var groundImage = drawGround(createImage(100, canvas.height - GROUND_AT), "#5F5", "#5A5", "#181", 4);
//-----------------------------
// create jelly
//-----------------------------
var jellyImage = drawRoundedBox(createImage(ep * 20, ep * 20), "#FA4", ep * 2, "black", ep * (3 / 5));
var jelly = createJellyBox(
jellyImage,
canvas.width / 2,
100,
0.1,
0.8,
1.2
);
// some more settings
ctx.imageSmoothingEnabled = true;
setFont(ctx, FONT_SIZE + FONT, "left");
//-----------------------------
// Main animtion loop
//-----------------------------
function updateAnim(){
// draw the background and ground
ctx.drawImage(skyImage, 0, 0, canvas.width, canvas.height)
ctx.drawImage(groundImage, 0, GROUND_AT, canvas.width, canvas.height - GROUND_AT)
// update and draw jelly
jelly.update();
jelly.draw(ctx);
// update and draw controls
controls.update();
// update jelly setting from controls.
jelly.wobbla = controls.wobbla.value;
jelly.bouncy = controls.bouncy.value;
jelly.react = controls.react.value;
jelly.sticky = controls.sticky.value;
// if the mouse is not busy then use left mouse click and drag to position jellu
if(controls.dragging < 0 && mouse.buttonRaw === 1){
controls.dragging = -2;
jelly.reset(mouse.x,mouse.y);
}else
if(controls.dragging === -2){ // release mouse
controls.dragging = -1;
}
if(!STOP){
requestAnimationFrame(updateAnim);
}else{
STOP = false;
}
}
updateAnim();
};
function resizeEvent(){
var waitForStopped = function(){
if(!STOP){ // wait for stop to return to false
jellyApp();
return;
}
setTimeout(waitForStopped,200);
}
STOP = true;
setTimeout(waitForStopped,100);
}
window.addEventListener("resize",resizeEvent);
jellyApp();

Categories

Resources