How do I create circular hotspots with two.js? - javascript

I am trying to make a solar system model. I made it all so far using two.js. I want to make it so when the mouse is over a planet's orbit path, it'll highlight that planet's orbit, and be a link to information about that planet. I'm having trouble finding a way to do this, though.
If you don't have it, here's Two.js.
My file structure looks like this:
css
main.css
js
main.js
two.js
index.html
PS. This will not render with the "Run code snippet" feature. If you wish to see it, you must save this code and put it in the same file structure I have.
//This work was created by the minds of Sam Steele and Devin Fowler of Cryptocosm Developers.
//Cryptocosm ©2014
//cryptocosm.x10.bz
//Initiate the render context
var elem = document.getElementById('canvas');
var two = new Two({
fullscreen: true
}).appendTo(elem);
//Define our planets and their colors
var sun = two.makeCircle(0, 0, 70);
var mercury = two.makeCircle(95, 0, 7);
var venus = two.makeCircle(125, 0, 8.5);
var earth = two.makeCircle(160, 0, 11.4);
var mars = two.makeCircle(200, 0, 9.5);
var jupiter = two.makeCircle(260, 0, 28);
// For Saturn we're going to do something special in order to get the rings
var saturnBody = two.makeCircle(320, 0, 24);
var saturnRings = two.makeCurve(296, 0, 290, 10, 322, 10, 350, -8, 342, -10, true);
saturnRings.rotation = 4.5;
var saturn = two.makeGroup(saturnBody, saturnRings);
var uranus = two.makeCircle(460, 0, 18);
var neptune = two.makeCircle(540, 0, 16);
var asteroid = two.makeCircle(0, 320, 3);
//Try to make some stars
var width = window.innerWidth;
var height = window.innerHeight;
var star;
for (i = 0; i < 200; i++) {
var randX = Math.round(Math.random() * width);
var randY = Math.round(Math.random() * height);
star = two.makeCircle(randX, randY, 2);
}
//Set the color of the planets
sun.fill = '#F7CA18';
mercury.fill = '#9E9E9E';
venus.fill = '#795548';
earth.fill = '#2196F3';
mars.fill = '#FF7043';
jupiter.fill = '#E67E22';
saturnBody.fill = '#A1887F';
saturnRings.stroke = "#F5F5F5";
saturnRings.linewidth = 7;
saturnRings.noFill();
saturn.translation.set(20, 0);
uranus.fill = '#4DB6AC';
neptune.fill = '#3F51B5';
star.fill = '#FAFAFA';
asteroid.fill = '#FAFAFA';
//Group the planets
var Mercury = two.makeGroup(mercury);
var Venus = two.makeGroup(venus);
var Earth = two.makeGroup(earth);
var Mars = two.makeGroup(mars);
var Jupiter = two.makeGroup(jupiter);
var Saturn = two.makeGroup(saturn);
var Uranus = two.makeGroup(uranus);
var Neptune = two.makeGroup(neptune);
var planets = two.makeGroup(sun, Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune);
//Center everything in the center of the element
planets.translation.set(two.width / 2, two.height / 2);
Mercury.rotation = 4;
Venus.rotation = 2.5;
Earth.rotation = 5.5;
Mars.rotation = 1;
Jupiter.rotation = 4.2;
Saturn.rotation = 2.5;
Uranus.rotation = 5.75;
Neptune.rotation = .5;
var counter = document.getElementById('counter');
var count = 0;
var yearsPassed = 0;
// Bind a function to scale and rotate the group to the animation loop.
two.bind('update', function (frameCount) {
//Set the "ZOOM" of the system
planets.scale = .8;
//Rotate all the planets
Mercury.rotation += .01607;
Venus.rotation += .01174;
Earth.rotation += .01;
/* //Earth year counter (not currently accurate at all)
count++;
if (count % 550 == 0) {
yearsPassed++;
counter.innerHTML = "An estimated " + yearsPassed + " Earth years passed";
}; */
Mars.rotation += .00802;
Jupiter.rotation += .00434;
Saturn.rotation += .00323;
Uranus.rotation += .00228;
Neptune.rotation += .00182;
//Rotate Saturn's rings so that it doesn't look dumb
saturnRings.rotation -= .00323;
}).play(); // Finally, start the animation loop
#font-face {
font-family: Roboto-Thin;
font-style: normal;
src: url(Roboto/Roboto-Thin.ttf);
}
html {
background-color: #212121;
}
body {
color: white;
font-family: Roboto-Thin;
}
#counter {
background-color: rgba(0, 0, 0, .4);
width: auto;
height: auto;
font-size: 34px;
float: left;
position: fixed;
padding: 10px;
}
#canvas {}
<html>
<head>
<title>The Solar System</title>
<link href="css/main.css" type="text/css" rel="stylesheet">
<script src="js/two.js"></script>
</head>
<body>
<div id="counter"></div>
<div id="canvas">
</div>
<script src="js/main.js"></script>
</body>
</html>

This is one way to achieve that effect:
This will only work for the svg renderer, but you can add event listeners on mouseover and mouseout as well as touchmove events like so:
// ... After you've created all the planets ...
// This command will create all the dom elements
// which you can bind events to.
two.update();
var highlight = function() { sun.fill = 'red'; };
var ignore = function() { sun.fill = 'yellow'; };
sun._renderer.elem.addEventListener('mousemove', highlight, false);
sun._renderer.elem.addEventListener('mouseout', ignore, false);
There is also an example of how to achieve this here.

Related

Tracking marker as mouse coordinates in JS (p5js)

I can scan the marker as I want to, however now I need my code to be able to scan the markers location and treat it as it is my mouseX and mouseY.
// https://kylemcdonald.github.io/cv-examples/
// more here:
// http://fhtr.org/JSARToolKit/demos/tests/test2.html
var capture;
var w = 640,
h = 480;
var raster, param, pmat, resultMat, detector;
function setup() {
pixelDensity(1); // this makes the internal p5 canvas smaller
capture = createCapture({
audio: false,
video: {
width: w,
height: h
}
}, function() {
console.log('capture ready.')
});
capture.elt.setAttribute('playsinline', '');
createCanvas(w, h);
capture.size(w, h);
capture.hide();
raster = new NyARRgbRaster_Canvas2D(canvas);
param = new FLARParam(canvas.width, canvas.height);
pmat = mat4.identity();
param.copyCameraMatrix(pmat, 100, 10000);
resultMat = new NyARTransMatResult();
detector = new FLARMultiIdMarkerDetector(param, 2);
detector.setContinueMode(true);
}
function draw() {
image(capture, 0, 0, w, h);
canvas.changed = true;
var thresholdAmount = 128; //select('#thresholdAmount').value() * 255 / 100;
detected = detector.detectMarkerLite(raster, thresholdAmount);
select('#markersDetected').elt.innerText = detected;
for (var i = 0; i < detected; i++) {
// read data from the marker
// var id = detector.getIdMarkerData(i);
// get the transformation for this marker
detector.getTransformMatrix(i, resultMat);
// convert the transformation to account for our camera
var mat = resultMat;
var cm = mat4.create();
cm[0] = mat.m00, cm[1] = -mat.m10, cm[2] = mat.m20, cm[3] = 0;
cm[4] = mat.m01, cm[5] = -mat.m11, cm[6] = mat.m21, cm[7] = 0;
cm[8] = -mat.m02, cm[9] = mat.m12, cm[10] = -mat.m22, cm[11] = 0;
cm[12] = mat.m03, cm[13] = -mat.m13, cm[14] = mat.m23, cm[15] = 1;
mat4.multiply(pmat, cm, cm);
// define a set of 3d vertices
var q = 1;
var verts = [
vec4.create(-q, -q, 0, 1),
vec4.create(q, -q, 0, 1),
vec4.create(q, q, 0, 1),
vec4.create(-q, q, 0, 1),
// vec4.create(0, 0, -2*q, 1) // poke up
];
// convert that set of vertices from object space to screen space
var w2 = width / 2,
h2 = height / 2;
verts.forEach(function (v) {
mat4.multiplyVec4(cm, v);
v[0] = v[0] * w2 / v[3] + w2;
v[1] = -v[1] * h2 / v[3] + h2;
});
noStroke();
fill(0, millis() % 255);
beginShape();
verts.forEach(function (v) {
vertex(v[0], v[1]);
});
endShape();
}
}
* {
font-family: sans-serif;
}
<html>
<head>
<meta charset="UTF-8">
<title>MarkerTracking</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/p5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/addons/p5.dom.min.js"></script>
<script src="//kig.github.io/JSARToolKit/demos/magi.js"></script>
<script src="//kig.github.io/JSARToolKit/JSARToolKit.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<h2>cv-examples Marker Tracking source</h2>
<p>Track an AR marker. Take a picture of one of these markers. Make sure it has a white border.</p>
<!-- <p>Threshold: <input type="range" id="thresholdAmount" value="50"></p>-->
<p>Markers detected: <span id="markersDetected"></span></p>
<script src="sketch.js"></script>
</body>
</html>
The snippet might give an error since it is trying to access the camera to scan the marker.
I have tried creating new variables and starting a brand new detection process which didn't work. I am open to any ideas.

How do i Make a sprite change with keyboard input i'm using vanilla JS

so ive been testing out HTML canvas. im trying to get a sprite to change on keyboard input.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<canvas id='Game' width='200' height='200' style='border: 2px solid #000000;'></canvas>
<script>
window.onload = function(){
var Game = document.getElementById('Game');
var context = Game.getContext('2d')
var room = new Image();
var lx = 0;
var ly = 0;
var li = 0;
var lo = 0;
var lwidth = 100;
var lheight = 100;
room.onload = function(){
context.drawImage(room,lx,ly,lwidth,lheight,li,lo,200,200);
}
room.src = 'https://i.ibb.co/D7fL7yN/Room.png';
var sprite = new Image();
var cx = 0;
var cy = 125;
var sy = 0;
var sx = 0;
var swidth = 35;
var sheight = 34;
sprite.onload = function(){
context.drawImage(sprite,sx,sy,swidth,sheight,cx,cy,50,50);
}
sprite.src = 'https://i.ibb.co/7VhjqPr/John-Sheet.png';
}
</script>
</body>
</html>
ive been searching on how to change the SX with Keyboard input so my character changes sprites. can you help me? a code example would be best!
Tracking keyboard state.
You can create an object that hold the state of the keyboard, specifically the keys you are interested in reacting to. Use the "keydown" and "keyup" KeyboardEvent to update the keyboard state as the events fire. Use the KeyboardEvent property code to workout which key is changing. DO NOT use keyCode as that has depreciated and is Non Standard
You also want to prevent the default behaviour of keys. Eg prevent arrow keys scrolling the page. This is done by calling the event preventDefault function
const keys = {
ArrowRight: false,
ArrowLeft: false,
ArrowUp: false,
ArrowDown: false,
}
addEventListener("keydown", keyEvent);
addEventListener("keyup", keyEvent);
function keyEvent(event) {
if (keys[event.code] !== undefined) {
keys[event.code] = event.type === "keydown";
event.preventDefault();
}
}
Then in the game you need only check the keyboard state
if (keys.ArrowRight) { moveRight() }
if (keys.ArrowLeft) { moveLeft() }
// and so on
In the demo below the keys are binded to game actions, meaning that what and how many keys are used are independent of the action. The are also set via configuration, so that key binding can be changed without changing game code. You can also bind other inputs as in example
Animation
To animate you should use the timer function requestAnimationFrame as it is specifically designed to give the best animation results. It will call your rendering function, you can consider the rendering function like a loop, that is call every time you step forward in animation time.
Putting it together
The demo below use the above (modified) methods to get keyboard input and render the scene via animation frame requests.
It also uses some techniques (simple versions of) that help make your game a better product.
Encapsulates the player as an object
Maintains game state by holding the current rendering function in currentRenderState
Has configuration config so all important values are in one place, and could be loaded (from JSON file) to easily change the game without changing code.
Has configurable keyboard binding, Note more than one key can be bound to a game action. In example movement is via WASD or arrow keys.
All text is configurable (making it language independent)
Passes the 2D context to all rendering code.
Separates the game from the rendering. This makes it easier to port the game to low end or high end devices or even move it to a server where ctx is replaced with coms and the game can be broadcast . The game does not change only how it is rendered
var currentRenderState = getFocus; // current game state
const config = {
player: {
start: {x: 100, y:100},
speed: 2,
imageURL: "https://i.stack.imgur.com/C7qq2.png?s=64&g=1",
},
keys: { // link key code to game action
up: ["ArrowUp", "KeyW"],
down: ["ArrowDown", "KeyS"],
left: ["ArrowLeft", "KeyA"],
right: ["ArrowRight", "KeyD"],
},
touchableTime: 140, // in ms. Set to 0 or remove to deactivate
text: {
focus: "Click canvas to get focus",
loading: "Just a moment still loading media!",
instruct: "Use arrow keys or WASD to move",
}
};
requestAnimationFrame(mainLoop); // request first frame
const ctx = gameCanvas.getContext("2d");
const w = gameCanvas.width, h = gameCanvas.height;
const player = {
image: (()=> {
const img = new Image;
img.src = config.player.imageURL;
img.addEventListener("load", () => player.size = img.width, {once: true});
return img;
})(),
x: config.player.start.x,
y: config.player.start.y,
size: 0,
speed: config.player.speed,
direction: 0,
update() {
var oldX = this.x, oldY = this.y;
if (actions.left) { this.x -= this.speed }
if (actions.right) { this.x += this.speed }
if (actions.up) { this.y -= this.speed }
if (actions.down) { this.y += this.speed }
if (this.x < 0) { this.x = 0 }
else if (this.x > w - this.size) { this.x = w - this.size }
if (this.y < 0) { this.y = 0 }
else if (this.y > h - this.size) { this.y = h - this.size }
const mx = this.x - oldX, my = this.y - oldY;
if (mx !== 0 || my !== 0) { this.direction = Math.atan2(my, mx) }
},
draw(ctx) {
if (ctx) {
ctx.setTransform(1, 0, 0, 1, this.x + this.size / 2, this.y + this.size / 2);
ctx.rotate(this.direction + Math.PI / 2); // rotate 90 deg as image points up
ctx.drawImage(this.image,-this.size / 2, -this.size / 2, this.size, this.size);
}
}
}
function drawText(ctx, text, size, color) {
if (ctx) {
ctx.fillStyle = color;
ctx.font = size + "px Arial";
ctx.textAlign = "center";
ctx.fillText(text, w / 2, h * (1/4));
}
}
function getFocus(ctx) {
drawText(ctx, config.text.focus, 24, "black");
}
function drawScene(ctx) {
if (!player.size === 0) {
drawText(ctx, config.text.loading, 16, "blue")
actions.hasInput = false; // ensure instruction are up when ready
} else {
if (!actions.hasInput) { drawText(ctx, config.text.instruct, 16, "blue") }
player.update();
player.draw(ctx);
}
}
function mainLoop() {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, w, h);
currentRenderState(ctx);
requestAnimationFrame(mainLoop); // request next frame
}
// keys holds action name for each named key. eg for up action ArrowUp: "up", KeyW: "up",
const keys = Object.entries(config.keys)
.reduce((keys, [action,codes]) =>
codes.reduce((keys, code) => (keys[code] = action, keys), keys), {});
// actions are set true when key down. NOTE first up key for action cancels action
const actions = Object.keys(config.keys)
.reduce((actions,action) => (actions[action] = false, actions),{});
addEventListener("keydown", keyEvent);
addEventListener("keyup", keyEvent);
function keyEvent(event) {
if (keys[event.code] !== undefined) {
actions[keys[event.code]] = event.type === "keydown";
event.preventDefault();
actions.hasInput = true;
}
}
if (config.touchableTime) {
const actionTimers = {};
touchable.addEventListener("click", (e) => {
if (e.target.dataset.action) {
actions[e.target.dataset.action] = true;
clearTimeout(actionTimers[e.target.dataset.action]);
actionTimers[e.target.dataset.action] = setTimeout(() => actions[e.target.dataset.action] = false, config.touchableTime);
actions.hasInput=true;
if (currentRenderState !== drawScene) {
window.focus();
currentRenderState = drawScene;
}
}
});
} else {
touchable.classList.add("hide");
}
gameCanvas.addEventListener("click", () => currentRenderState = drawScene, {once: true});
canvas {border: 1px solid black}
#game {
width:402px;
height:182px;
font-size: 24px;
user-select: none;
}
.left {
position: absolute;
top: 160px;
left: 10px;
cursor: pointer;
}
.right {
position: absolute;
top: 160px;
left: 355px;
cursor: pointer;
}
#touchable span:hover {color: red}
.hide { display: none }
<div id="game">
<canvas id="gameCanvas" width="400" height="180"></canvas>
<div id="touchable">
<div class="left">
<span data-action="up">▲</span>
<span data-action="down">▼</span>
</div>
<div class="right">
<span data-action="left">◄</span>
<span data-action="right">►</span>
</div>
</div>
</div>
Click to snippet frame area for focusing keyboard events
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<canvas id='Game' width='200' height='200' style='border: 2px solid #000000;'></canvas>
<script>
window.onload = function(){
// Keyboard collect
const keys = [];
document.onkeydown = e => {
var code = e.which;
if(keys.indexOf(code) < 0){
keys.push(code);
}
};
document.onkeyup = e => keys.splice(keys.indexOf(e.which),1);
// constants
const Game = document.getElementById('Game');
const context = Game.getContext('2d')
const room = new Image();
const lx = 0;
const ly = 0;
const li = 0;
const lo = 0;
const lwidth = 100;
const lheight = 100;
room.onload = function(){
context.drawImage(room,lx,ly,lwidth,lheight,li,lo,200,200);
}
room.src = 'https://i.ibb.co/D7fL7yN/Room.png';
const sprite = new Image();
const swidth = 35;
const sheight = 34;
const sy = 0;
sprite.onload = function(){
context.drawImage(sprite,0,sy,swidth,sheight,0,cy,50,50);
}
sprite.src = 'https://i.ibb.co/7VhjqPr/John-Sheet.png';
// variables
let cx = 0;
let cy = 125;
let sx = 0;
// new variables
const frames_per_step = 20;
let moving = false; // moving flag
let step = 0; // frame counter (for steps)
// main loop function
function tick() {
// keyboard process
if (keys.length) {
keys.forEach(item => {
switch(item){
case 68:case 39://D and right arrow
cx += 1; // move right
// change sprite
if (step++ < frames_per_step / 2) {
sx = 35; // leg up
} else {
sx = 70; // leg down
if(step > frames_per_step) step = 0;
}
moving = true;
break;
case 65:case 37://A and left arrow
cx -= 1; // move left
// change sprite
if (step++ < frames_per_step / 2) {
sx = 105;
} else {
sx = 140;
if(step > frames_per_step) step = 0;
}
moving = true;
break;
// no sprite mechanics here, just move
case 87:case 38://W adn arrow up
cy -= 1;
break;
case 83:case 40://S adn arrow down
cy += 1;
break;
}
});
// render
context.drawImage(room,lx,ly,lwidth,lheight,li,lo,200,200);
context.drawImage(sprite,sx,sy,swidth,sheight,cx,cy,50,50);
} else if(moving) { // return sprite to stay position
sx = 0;
context.drawImage(room,lx,ly,lwidth,lheight,li,lo,200,200);
context.drawImage(sprite,sx,sy,swidth,sheight,cx,cy,50,50);
moving = false;
} // else do nothing
requestAnimationFrame(tick);
}
tick();
}
</script>
</body>
</html>

Average color of an image of selected region giving wrong image

I am trying to find the average color of some part of the image and then setting it as a background. I am using this color to find the selected region average code.
JSFiddle code to find average image color.
I want to extract that part from the image and find the average color then set it to the background image
This is the code i am using
<img id="i" src="bg_300x250_landscape.jpg"/>
<script type="text/javascript">
var rgb = getAverageRGB(document.getElementById('i'));
document.body.style.backgroundColor = 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')';
function getAverageRGB(img) {
var canvas = document.createElement('canvas'),
context = canvas.getContext && canvas.getContext('2d'),
rgb = {r:102,g:102,b:102}, // Set a base colour as a fallback for non-compliant browsers
pixelInterval = 5, // Rather than inspect every single pixel in the image inspect every 5th pixel
count = 0,
i = -4,
data, length;
// return the base colour for non-compliant browsers
if (!context) { return rgb; }
context.drawImage(img, 146, 214);
try {
data = context.getImageData(146, 214, 70, 20);
} catch(e) {
// catch errors - usually due to cross domain security issues
alert(e);
return rgb;
}
data = data.data;
length = data.length;
while ((i += pixelInterval * 4) < length) {
count++;
rgb.r += data[i];
rgb.g += data[i+1];
rgb.b += data[i+2];
}
// floor the average values to give correct rgb values (ie: round number values)
rgb.r = Math.floor(rgb.r/count);
rgb.g = Math.floor(rgb.g/count);
rgb.b = Math.floor(rgb.b/count);
return rgb;
}
</script>
I have used MeasureIT Firefox AddOn to get the cordinates and i found
x:146
y:214
width:70
height:20
which i have used here.
context.drawImage(img, 146, 214);
...
data = context.getImageData(146, 214, 70, 20);
So the average color of that region should be green but i am getting the color of the sky.
I wrote an example on jsfiddle : http://jsfiddle.net/w86mu4bz
Here is the code :
html :
<img src="http://publicdomainarchive.com/wp-content/uploads/2014/02/public-domain-images-free-high-resolution-quality-photos-unsplash-0079.jpg" title="Photo from publicdomainarchive.com" id="image" crossOrigin="anonymous"/>
<h1 id="h1">My amazing title</h1>
css :
img, h1 {
margin : 0;
padding : 0;
}
img {
width : 500px;
position : absolute;
top : 0;
left : 0;
z-index : 1;
}
h1 {
font-family : 'Open Sans', Arial;
font-size : 2em;
position : absolute;
top : 20px;
left : 0;
color : white;
z-index : 2;
padding : 3px 5px 3px 25px; /* attention this causes a margin of error !! */
}
And javascript :
var average_color_background = function(image, title) {
var treat_properties = function(elmt_propertie) {
return parseInt(elmt_propertie.replace(/px/, ''));
}
var image_width = treat_properties(getComputedStyle(image, null).width),
image_height = treat_properties(getComputedStyle(image, null).height),
title_width = treat_properties(getComputedStyle(title, null).width),
title_height = treat_properties(getComputedStyle(title, null).height),
title_top = treat_properties(getComputedStyle(title, null).top),
title_left = treat_properties(getComputedStyle(title, null).left);
var c = document.createElement('canvas');
c.width = image_width;
c.height = image_height;
c.style.position = "absolute";
c.style.top = 0;
c.style.left = 0;
c.style.zIndex = 0; // invisible calculation
document.getElementsByTagName('body')[0].appendChild(c);
var ctx = c.getContext("2d");
var image_bis = new Image();
image_bis.crossOrigin = 'anonymous'; // avoid security error
image_bis.onload = function(){
ctx.drawImage(image_bis,0,0,image_width,image_height);
var imageData = ctx.getImageData(title_left, title_top, title_width, title_height),
mapPixel = imageData.data;
var red = 0,
green = 0,
blue = 0,
length = 4 * title_width * title_height;
for(var i=0;i<length;i+=4) {
red += mapPixel[i];
green += mapPixel[i+1];
blue += mapPixel[i+2];
}
length = length / 4;
red = Math.round(red/length);
green = Math.round(green/length);
blue = Math.round(blue/length);
// display result, can easily be improved for something more beautiful (e.g. using complementary color):
title.style.backgroundColor = 'rgb('+red+','+green+','+blue+')';
c.parentNode.removeChild(c);
return true;
}
image_bis.src = image.src;
}
average_color_background(document.getElementById('image'),document.getElementById('h1'));
It uses canvas, so don't forget to add a default color for browsers that don't support it (using modernizr for example)

Binding Canvas images to events based on class

I have the following fiddle here: http://jsfiddle.net/tbt0jspd/2/
The idea is to display a series of boxes on the screen using the box image that will have a class of box on them but ONE will have a class of win and the rest will have a class of lose.
This is how I handle adding the boxes to the canvas element:
var boxes = 6;
var boxImg = new Image();
boxImg.src = 'img/box.png';
boxImg.onload = function() {
for (var i = 0, x = 20, y = 200; i < boxes; x += 148, y = 200, i++) {
context.drawImage(boxImg, x, y);
}
};
The first question is how do I add the classes as described above to the images? They should all have a class of box, and 5 should have a class of lose, and 1 a class of win.
Users will then click these boxes and a class of opened will be applied to them in turn. The boxes will change to a different image depending on their class of win or lose.
$('.box').not('.opened').on('click', function() {
if( $(this).hasClass('win') ) {
$(this).src = 'img/box-win.png';
} else if( $(this).hasClass('lose') ) {
$(this).src = 'img/box-lose.png';
}
$(this).addClass('opened');
if( $('.opened').length == boxes )
{
alert('all boxes open');
}
});
You can't have classes on boxes that or on canvas, because after you draw an image on canvas, it becomes nothing more but a set of pixels.
What you can do, however, is modify which image gets drawn on the canvas based on its class (or another property), but you'll have to redraw it on the canvas whenever it's changed.
Redrawing them on canvas is the key.
For easier control, you might consider using an object to represent boxes (not just their images), so you can easily toggle their states/images/etc.
UPDATE
Here is a simple example using the button to open all the boxes and assign them win/lose state randomly: http://jsfiddle.net/d7ov9pak/
var boxes = 6;
var canvas = document.getElementById('OpenTheBox');
var context = canvas.getContext('2d');
var boxImg = new Image();
boxImg.src = 'http://i59.tinypic.com/vqp4c7.png';
var winImg = new Image();
winImg.src = 'http://i60.tinypic.com/2ujtr0i.png';
var loseImg = new Image();
loseImg.src = 'http://i60.tinypic.com/oivsc0.png';
var cwidth = canvas.width = window.screen.width;
var cheight = canvas.height = window.screen.height;
var ctop = canvas.offsetTop;
var cleft = canvas.offsetLeft;
context.fillStyle = '#000000';
context.fillRect(0, 0, cwidth, cheight);
canvas.oncontextmenu = function (e) {
e.preventDefault();
};
boxImg.onload = function () {
for (var i = 0, x = 20, y = 200; i < boxes; x += 148, y = 200, i++) {
context.drawImage(boxImg, x, y);
}
};
function openBoxes() {
// clear the canvas, just in case
canvas.width = canvas.width;
for (var i = 0, x = 20, y = 200; i < boxes; x += 148, y = 200, i++) {
// assign a random win/lose box
var image = (Math.random() >= 0.5) ? winImg : loseImg;
context.drawImage(image, x, y);
}
}
* {
margin: 0;
padding: 0;
border: 0;
outline: 0;
}
html, body {
width: 100%;
height: 100%;
overflow: hidden;
background: #000000;
}
<button onclick="openBoxes()">Open the boxes</button>
<canvas id="OpenTheBox"></canvas>
Note, if this is all there is to it and you don't need any other canvas functionality, perhaps you should consider not using the canvas, and simply manipulate DOM element to show different images based on CSS classes.

HTML5 Canvas & javascript prize wheel

I am having trouble trying to implement a "prize wheel," using canvas. I am using something similar to the canvas "roulette wheel" http://jsfiddle.net/wYhvB/4/ that is floating around StackOverflow. My dilema is when you click spin, in the background I make an API call that returns an id of which prize should actually be chosen, the interface is nothing more than eye candy. I am pushing all the prize descriptions into the first array, how can I add an id into each one of the arcs and stop on a particular one instead of stopping on a specifically random time? I.E. If the API returns "car," I want this wheel to spin a few times and stop on car.
var colors = ["##eaeaea", "##cccccc", "##eaeaea", "##cccccc",
"##eaeaea", "##cccccc", "##eaeaea", "##cccccc"];
// NEED to pre load this data prior
var prize_descriptions = ["car","house","etc..."]; // These are injected on an init call from an api
var current_user_status = {};
var startAngle = 0;
var arc = Math.PI / 4;
var spinTimeout = null;
var spinArcStart = 10;
var spinTime = 0;
var spinTimeTotal = 0;
var current_user_status = null;
var spin_results = null;
var ctx;
function drawSpinnerWheel() {
var canvas = document.getElementById("canvas");
if (canvas.getContext) {
var outsideRadius = 200;
var textRadius = 160;
var insideRadius = 125;
ctx = canvas.getContext("2d");
ctx.clearRect(0,0,500,500);
ctx.strokeStyle = "black";
ctx.lineWidth = 2;
ctx.font = 'bold 12px Helvetica, Arial';
for(var i = 0; i < 8; i++) {
var angle = startAngle + i * arc;
ctx.fillStyle = colors[i];
ctx.beginPath();
ctx.arc(250, 250, outsideRadius, angle, angle + arc, false);
ctx.arc(250, 250, insideRadius, angle + arc, angle, true);
ctx.stroke();
ctx.fill();
ctx.save();
ctx.shadowOffsetX = -1;
ctx.shadowOffsetY = -1;
ctx.shadowBlur = 0;
ctx.shadowColor = "rgb(220,220,220)";
ctx.fillStyle = "black";
ctx.translate(250 + Math.cos(angle + arc / 2) * textRadius,
250 + Math.sin(angle + arc / 2) * textRadius);
ctx.rotate(angle + arc / 2 + Math.PI / 2);
var text = prize_descriptions[i];
if (text == undefined)
text = "Not this time! "+i;
ctx.fillText(text, -ctx.measureText(text).width / 2, 0);
ctx.restore();
}
//Arrow
ctx.fillStyle = "black";
ctx.beginPath();
ctx.moveTo(250 - 4, 250 - (outsideRadius + 5));
ctx.lineTo(250 + 4, 250 - (outsideRadius + 5));
ctx.lineTo(250 + 4, 250 - (outsideRadius - 5));
ctx.lineTo(250 + 9, 250 - (outsideRadius - 5));
ctx.lineTo(250 + 0, 250 - (outsideRadius - 13));
ctx.lineTo(250 - 9, 250 - (outsideRadius - 5));
ctx.lineTo(250 - 4, 250 - (outsideRadius - 5));
ctx.lineTo(250 - 4, 250 - (outsideRadius + 5));
ctx.fill();
}
}
function spin() {
spinAngleStart = Math.random() * 10 + 10;
spinTime = 0;
spinTimeTotal = Math.random() * 3 + 4 * 1000;
rotateWheel();
}
function rotateWheel() {
spinTime += 30;
if(spinTime >= spinTimeTotal) {
stopRotateWheel();
return;
}
var spinAngle = spinAngleStart - easeOut(spinTime, 0, spinAngleStart, spinTimeTotal);
startAngle += (spinAngle * Math.PI / 180);
drawSpinnerWheel();
spinTimeout = setTimeout('rotateWheel()', 30);
}
function stopRotateWheel() {
clearTimeout(spinTimeout);
var degrees = startAngle * 180 / Math.PI + 90;
var arcd = arc * 180 / Math.PI;
var index = Math.floor((360 - degrees % 360) / arcd);
ctx.save();
ctx.font = 'bold 30px Helvetica, Arial';
var text = prize_descriptions[index];
ctx.fillText(text, 250 - ctx.measureText(text).width / 2, 250 + 10);
ctx.restore();
}
function easeOut(t, b, c, d) {
var ts = (t/=d)*t;
var tc = ts*t;
return b+c*(tc + -3*ts + 3*t);
}
drawSpinnerWheel();
$("#spin").bind('click', function(e) {
e.preventDefault();
spin();
});​
I have some experience in creating HTML5 canvas winning wheels, the way that I solved the problem of getting the wheel to stop at at a particular prize determined by a server-side process was to define an array of prizes that correspond to what is shown on the wheel, specifying the start and end angle of each prize in degrees, and then before the wheel is rotated setting a targetAngle to a random value between the start and end angle of the prize in question plus adding a multiple of 360 degrees so that the wheel spins a few times before slowing to a stop at the predetermined prize.
For example if the wheel has 4 prizes then the prizes array is...
var prizes = new Array();
prizes[0] = {"name" : "Prize 1", "startAngle" : 0, "endAngle" : 89};
prizes[1] = {"name" : "Prize 2", "startAngle" : 90, "endAngle" : 179};
prizes[2] = {"name" : "Prize 3", "startAngle" : 180, "endAngle" : 269};
prizes[3] = {"name" : "Prize 4", "startAngle" : 270, "endAngle" : 359};
And the code to set the targetAngle is something like...
targetAngle = Math.floor(prizes[determinedPrize]['startAngle'] + (Math.random() * (prizes[determinedPrize]['endAngle'] - prizes[determinedPrize]['startAngle'])));
targetAngle += (360 * 18);
The spinning function of the wheel then loops until the current angle of the wheel equals the targetAngle.
A working example of my prize wheel and the fully commented source code is available at http://www.dougtesting.net. The pre-determined feature is not enabled in the online example, but can easily be turned on in the source code (winwheel.js) once downloaded.
The easiest way I can think of is to take the current position of the wheel, then calculate the distance from this point to the prize. Add a random number of multiples of the wheel diameter circumference and then you have a distance. The edge of the wheel must travel through this distance to end up at the prize.
Just like you can use linear or cubic interpolation to move an element from 1 position to another in a specified number of steps, you can use the same approach to rotate the wheel from point 0 (start point) to point 1 (end point) from time=0 to time=1
This page Math: Ease In, ease Out a displacement using Hermite curve with time constraint is a good read. It's where I managed to wrap my head around doing basically the same thing - just up/down/left/right, rather than rotationally.
It's a bit choppy while im look at iot just now. Dont know if it's jsfiddle, the missing images or the 25 browser tabs & programs I have running.
Anyway, the point is to use non-linear interpolation to get to a specified distance away in a specified number of steps. It should get there in a specified time, but not with 25 windows open.. :laughs:
Check out the SO link above. It's got some great pictures that really explain quite well.
Here's a fiddle of cubic-spline interpolation for the time.
http://jsfiddle.net/enhzflep/XKzGF/
And here's the full code:
<!DOCTYPE html>
<html>
<head>
<script>
var continuePlaying = true, isPlaying=false;
function byId(a){return document.getElementById(a)}
function myInit()
{
}
window.addEventListener("load",myInit,!1);
function cubicHermite(a,b,d,e,c){var g=a*a,f=g*a;return(2*f-3*g+1)*b+(f-2*g+a)*e+(-2*f+3*g)*d+(f-g)*c}
function interp(a,b,d,e,c){var g,f;f=e/(a/2+b+d/2);g=f*a/2;f*=b;return result=c<=a?cubicHermite(c/a,0,g,0,f/b*a):c<=a+b?g+f*(c-a)/b:cubicHermite((c-a-b)/d,g+f,e,f/b*d,0)}
function linear(a){return a}
function cubic(a){return interp(0.35,0.3,0.35,1,a)}
function getSize(a){return{left:a.offsetLeft,top:a.offsetTop,width:a.clientWidth,height:a.clientHeight}}
function doAnim2(a,b,d,e){var c=a/b;setTimeout(function(){doAnimStep(0,b,c,d,e)},c)}
function doAnimStep(a,b,d,e,c){a<=b?(setTimeout(function(){doAnimStep(a,b,d,e,c)},d),e(a/b),a++):void 0!=c&&null!=c&&c()}
//scroll with cubic interpolation of the current scroll position
function cubicScrollDown(b,callback)
{
var a=byId(b),c=a.scrollHeight-a.clientHeight;
doAnim2(500,c,function(b){a.scrollTop=c*cubic(b)},callback);
}
function cubicScrollUp(b,callback)
{
var a=byId(b),c=a.scrollHeight-a.clientHeight;
doAnim2(500,c,function(b){ a.scrollTop=c*(1-cubic(b)) },callback );
}
//scroll with cubic interpolation of the current scroll position
function linearScrollDown(b, callback)
{
var a=byId(b),c=a.scrollHeight-a.clientHeight;
doAnim2(500,c,function(b){a.scrollTop=c*linear(b)}, callback);
}
function linearScrollUp(b, callback)
{
var a=byId(b),c=a.scrollHeight-a.clientHeight;
doAnim2(1000,c,function(b){ a.scrollTop=c*(1-linear(b)) }, callback );
}
function animFadeOut(elem, callback)
{
doAnim2(500,50,function(raw){elem.style.opacity=1-cubic(raw)},callback);
}
function animFadeIn(elem, callback)
{
doAnim2(500,50,function(raw){elem.style.opacity=cubic(raw)},callback);
}
function cubicBounce(b)
{
cubicScrollDown(b, downCallback);
function downCallback()
{
cubicScrollUp(b, upCallback);
}
function upCallback()
{
if (continuePlaying===true)
setTimeout( function(){cubicBounce(b);}, 0);
else
continuePlaying = true;
}
}
function linearBounce(b)
{
linearScrollDown(b, downCallback);
function downCallback()
{
linearScrollUp(b, upCallback);
}
function upCallback()
{
if (continuePlaying===true)
setTimeout( function(){linearBounce(b);}, 0);
else
continuePlaying = true;
}
}
function fadeOutIn(tgtListIdStr)
{
var tgt = byId(tgtListIdStr);
animFadeOut(tgt,fadedOutCallback);
function fadedOutCallback()
{
animFadeIn(tgt);
}
}
function prependChild(parent, element)
{
if (parent.childNodes)
parent.insertBefore(element, parent.childNodes[0]);
else
parent.appendChild(element)
}
function slideUpRemove(tgtListIdStr)
{
var tgt = byId(tgtListIdStr);
var listItems = tgt.getElementsByTagName('li');
mHeight = listItems[0].clientHeight;
animFadeOut(listItems[0], slideUp);
function slideUp()
{
doAnim2(500, 50, slideUpStep, slideUpDone);
function slideUpStep(raw)
{
listItems[0].style.height = (cubic(1-raw) * mHeight) + 'px';
}
function slideUpDone()
{
dummy = listItems[0];
tgt.appendChild(dummy);
//dummy.removeAttribute('style');
dummy.style.height = null;
dummy.style.opacity = null;
}
}
}
function slideDownInsert(tgtListIdStr)
{
// get the container, it's items and the height of the last LI item.
var tgt = byId(tgtListIdStr);
var listItems = tgt.getElementsByTagName('li');
mHeight = listItems[listItems.length-1].clientHeight;
// create a dummy to take the place of the last item, set it's size and height. make it the first child of the containing list
var dummy = document.createElement('li');
dummy.style.opacity = 0;
dummy.style.height = 0 + 'px';
prependChild(tgt, dummy);
// animate it!
doAnim2(500, 50, slideDownStep,slideDownDone);
function slideDownStep(raw)
{
dummy.style.height = (cubic(raw) * mHeight)+'px';
}
function slideDownDone()
{
// remove the dummy
var newItem = listItems[listItems.length-1];
newItem.style.opacity = 0;
prependChild(tgt, newItem);
tgt.removeChild(dummy);
animFadeIn(newItem, function(){newItem.removeAttribute('style')});
}
}
</script>
<style>
#myListDiv
{
width: 256px;
padding: 6px;
height: 128px;
overflow-y: hidden; /*scroll;*/
border-radius: 6px;
border: solid 1px transparent;
border-color: rgba(0,0,0,0.2) rgba(255,255,255,0.4) rgba(255,255,255,0.4) rgba(0,0,0,0.2);
/* background-image: url(img/rss128.png); */
background-color: rgba(0,0,0,0.1);
}
h4, p
{
margin: 6px 0;
}
ul
{
padding: 0;
list-style: none;
margin: 0;
}
ul#mList li
{
padding: 0 8px;
margin: 0 6px;
display: block;
border: solid 1px #cccccc;
border-bottom-color: #000;
border-color: #ccc transparent #000 transparent;
vertical-align: middle;
background-color: rgba(150,150,150,0.95);
overflow: hidden;
}
.thumb
{
width: 48px;
height: 48px;
float: left;
}
.thumb img
{
height: 48px;
}
#mPanel
{
display: inline-block;
float: left;
padding: 32px;
background-color: hsl(80,50%,20%);
}
</style>
</head>
<body>
<div id='mPanel'>
<div id='myListDiv'>
<ul id='mList'>
<li><div class='thumb'><img src='img/opera.svg'></div><div class='itemData'><h4><a>Item #1</a></h4><p>some assorted text</p></div></li>
<li><div class='thumb'><img src='img/chromeEyes.svg'></div><h4><a>Item #2</a></h4><p>some assorted text</p></li>
<li><div class='thumb'><img src='img/girl.png'></div><h4><a>Item #3</a></h4><p>some assorted text</p></li>
<li><div class='thumb'><img src='img/chuck-norris.jpg'></div><h4><a>Item #1</a></h4><p>some assorted text</p></li>
<li><div class='thumb'><img src='img/redBaron.jpg'></div><h4><a>Item #2</a></h4><p>some assorted text</p></li>
<li><div class='thumb'><img src='img/default.png'></div><h4><a>Item #3</a></h4><p>some assorted text</p></li>
</ul>
</div>
</div>
<button onclick='cubicScrollDown("myListDiv")'>Cubic down</button>
<button onclick='cubicScrollUp("myListDiv")'>Cubic up</button><br>
<button onclick='cubicBounce("myListDiv")'>cubic bounce</button>
<button onclick='linearBounce("myListDiv")'>linear bounce</button><br>
<input type='button' onclick='slideUpRemove("mList")' value='newest'/>
<input type='button' onclick='slideDownInsert("mList")' value='Slide Down'/><br>
<button onclick='continuePlaying=false'>Stop Anim cycle</button>
<input type='button' onclick='fadeOutIn("mList");' value='fadeOutIn'/><br>
</body>
</html>

Categories

Resources