See my example: https://jsfiddle.net/ksumarine/x7f9yLb7/4/
I have a .Stage that has an offset which puts 0, 0 at the bottom center of the canvas. This has a .Layer which draws the .Shape of an objects path from an array of x,y coordinates.
let routes = [{x: #, y: #}, {x: #, y: #}, ......] // example routes
let testStage = new Konva.Stage({
container: con,
x: 0,
y: 0,
width: 300,
height: 300,
offsetX: ((300 / 2) * -1),
offsetY: (300 * -1)
});
let testLayer = new Konva.Layer();
let testShape = new Konva.Shape({
sceneFunc: function(context) {
context.beginPath();
for (var i = 0; i < routes.length - 1; i++) {
context.moveTo(routes[i].x, routes[i].y);
context.lineTo(routes[i + 1].x, routes[i + 1].y);
}
context.strokeStyle = '#000';
context.lineWidth = 1.5;
context.stroke();
}
});
testLayer.add(testShape);
testStage.add(testLayer);
//////////// here's the button click event
animateButton.addEventListener('click', e => {
let i = 0;
let _ctx = testLayer.getContext()._context;
let draw = () => {
setTimeout(() => {
if (routes.length) {
_ctx.clearRect(0, 0, 300, 300);
_ctx.beginPath();
_ctx.moveTo(routes[0].x, (routes[0].y * -1));
for (let p = 1, plen = i; p < plen; p++) {
_ctx.lineTo(routes[p].x, (routes[p].y * -1));
}
_ctx.strokeStyle = '#000';
_ctx.lineWidth = 1.5;
_ctx.stroke();
(i === routes.length - 1) ? i = 0: i++;
}
requestAnimationFrame(draw);
}, 1000 / 59.940);
}
draw();
});
Everything at this point works well and the path is drawn as expected. I would like to use Konva.Animation to show how the object moved, but I cannot figure it out or find any examples to help.
I've added normal Canvas JS in the button eventListener as an example of the desired effect, but using the testLayer context to draw with puts 0,0 back at the top left...not what I want.
Could someone point me in the right direction to accomplish this using Konva.Animation?
For those coming across this question it was asked again in Nov 18, and this time there are two answers. See them here
Related
I made this red line in JavaScript that goes to closest target (balloon 1 to 3) to the player but I need to make it so that it moves like a laser starting from player position into the target position. I thought about multiple ways of implementing this with no luck.
function Tick() {
// Erase the sprite from its current location.
eraseSprite();
for (var i = 0; i < lasers.length; i++) {
lasers[i].x += lasers[i].direction.x * laserSpeed;
lasers[i].y += lasers[i].direction.y * laserSpeed;
//Hit detection here
}
function detectCharClosest() {
var ballon1char = {
x: balloon1X,
y: balloon1Y
};
var ballon2char = {
x: balloon2X,
y: balloon2Y
};
var ballon3char = {
x: balloon3X,
y: balloon3Y,
};
ballon1char.distanceFromPlayer = Math.sqrt((CharX - balloon1X) ** 2 + (CharY - balloon1Y) ** 2);
ballon2char.distanceFromPlayer = Math.sqrt((CharX - balloon2X) ** 2 + (CharY - balloon2Y) ** 2);
ballon3char.distanceFromPlayer = Math.sqrt((CharX - balloon3X) ** 2 + (CharY - balloon3Y) ** 2);
var minDistance = Math.min(
ballon1char.distanceFromPlayer,
ballon2char.distanceFromPlayer,
ballon3char.distanceFromPlayer);
console.log(ballon1char);
console.log(ballon2char);
console.log(ballon3char);
for (let i = 0; i < 3; i++) {
if (minDistance == ballon1char.distanceFromPlayer)
return ballon1char
if (minDistance == ballon2char.distanceFromPlayer)
return ballon2char
if (minDistance == ballon3char.distanceFromPlayer)
return ballon3char
}
}
function loadComplete() {
console.log("Load is complete.");
canvas = document.getElementById("theCanvas");
ctx = canvas.getContext("2d");
myInterval = self.setInterval(function () { Tick() }, INTERVAL);
myInterval = self.setInterval(function () { laserTicker(detectCharClosest()) }, 2000);
function laserTicker(balloon) {
//gets the closest ballon to go to
laserDo(balloon);
}
function laserDo(balloon) {
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = "#F44336"; // "red";
ctx.moveTo(CharX + 16, CharY + 16);
ctx.lineTo(balloon.x, balloon.y);
// lasers.push({x: })
ctx.stroke();
}
I didn't put all of my code here so If something doesn't make sense please tell me. I'm still new to JavaScript and learning it. One way I thought I could make this work was by taking the distance between the player and the target and dividing it by the speed on the x and y axis then changing having it start from the player position and keeps on adding up on both axis until it reaches the target. That didn't work out though. If you have any suggestions then please tell me.
Thanks
I'm making a 3D box with Zdog and I want to let the height of the box grow.
Here is a codepen: https://codepen.io/anon/pen/qzBMgp.
This is my code for the box:
let progressBox = new Zdog.Box({
addTo: progress,
width: 200,
height: boxHeight,
depth: 200,
stroke: 1,
})
This is the code I use to increase the height of the box. If the box is shorter than 400, the box will increase its height with 0.1.
function animate() {
if (boxHeight < 400) {
moveUp = 'up';
} else if (boxHeight > 400) {
moveUp = 'false';
}
boxHeight += moveUp == 'up' ? 0.1 : 0;
}
The problem is that the box stays at a height of 0 (the value I gave to boxHeight), but when I console.log(boxHeight) the boxHeight will grow.
First of all, let me point out that you cannot change the value of a constant . On your code, the boxHeight is declared as const.
Second, you will need to use Zdog's copy() method. Here is your code modified accordingly.
Zdog.Anchor.prototype.renderGraphSvg = function (svg) {
if (!svg) {
throw new Error('svg is ' + svg + '. ' +
'SVG required for render. Check .renderGraphSvg( svg ).');
}
this.flatGraph.forEach(function (item) {
item.render(svg, Zdog.SvgRenderer);
});
};
const TAU = Zdog.TAU;
const light = '#EAE2B7';
const yellow1 = '#FCBF49';
const orange1 = '#F77F00';
const red1 = '#d62828';
const purple1 = '#003049';
const white1 = '#ffffff';
const isSpinning = true;
var boxHeight = 0;
let progress = new Zdog.Illustration({
element: '.progress',
dragRotate: true,
translate: {
y: 25
},
rotate: {
x: -0.4, y: 0.75
}
});
let progressBox = new Zdog.Box({
addTo: progress,
width: 200,
depth: 200,
height: boxHeight,
stroke: 1,
color: purple1, // default face color
leftFace: yellow1,
rightFace: orange1,
topFace: red1,
bottomFace: light,
translate: {
x: 0,
y: 300
},
});
function animate() {
if (boxHeight <= 400) {
boxHeight++; // 1
progressBox = progressBox.copy({
height: boxHeight, // overwrite height
translate: {
y: progressBox.translate.y - 1 // overwrite vertical position to put box in place while height is growing.
}
});
}
progress.updateRenderGraph();
requestAnimationFrame(animate);
}
animate();
I forked your pen and updated it with the code above. See Zdog - progress box
Note that it seems to be expensive doing the copy() method on every animation frame. I am also new to this library and this is currently the fix I know of.
This question already has answers here:
clearRect function doesn't clear the canvas
(5 answers)
Closed 3 years ago.
I want an HTML canvas that displays mouse position on its JS grid on mouse move, but I can't seem to clear my canvas, I tried using ctx.clearRect(0,0 canvas.width, canvas.height) and clearing with a click, but it somehow remembers the previous draw it had. I want only one black square to be displayed on canvas at a time depending on the mouse position. Heres demo on code pen and some code
<canvas id="myMap" style="width: 300px;height: 300px;background-color: beige;"></canvas>
<script>
var findDivisible = (x, scale) => {
while (x % scale !== 0 && x > 0) {
x = x - 1;
};
return x
};
var map = document.getElementById("myMap");
map.width = 300;
map.height = 300;
var mapContext = document.getElementById("myMap").getContext("2d");
map.addEventListener("mousemove", function(e) {
mapContext.clearRect(0, 0, map.width, map.height);
mapContext.rect(findDivisible(e.clientX - map.offsetLeft, 50), findDivisible(e.pageY - map.offsetTop, 50), 50, 50);
mapContext.stroke();
});
map.addEventListener("click", function() {
mapContext.clearRect(0, 0, 500, 500);
})
</script>
You're not starting a new stroke for each rectangle, but "piling them up" so they get re-re-redrawn with .stroke().
Use .beginPath():
function findDivisible(x, scale) {
while (x % scale !== 0 && x > 0) {
x = x - 1;
}
return x;
}
var map = document.getElementById("myMap");
map.width = 300;
map.height = 300;
var mapContext = map.getContext("2d");
map.addEventListener("mousemove", function(e) {
mapContext.clearRect(0, 0, map.width, map.height);
mapContext.beginPath();
mapContext.rect(
findDivisible(e.clientX - map.offsetLeft, 50),
findDivisible(e.pageY - map.offsetTop, 50),
50,
50,
);
mapContext.stroke();
});
map.addEventListener("click", function() {
mapContext.clearRect(0, 0, 500, 500);
});
<canvas id="myMap" style="width: 300px;height: 300px;background-color: beige;"></canvas>
You can try this:
map.addEventListener("mousemove", function(e){
map.width = map.width;
mapContext.rect(findDivisible(e.clientX-map.offsetLeft,50) , findDivisible(e.pageY - map.offsetTop,50), 50, 50);
mapContext.stroke();
});
map.addEventListener("click", function(){
map.width = map.width;
});
One of the ways that is implicitly endorsed in the spec and often used in people’s apps to clear a canvas
https://dzone.com/articles/how-you-clear-your-html5
I'm trying to create an infinite looping canvas based on a main 'grid'. Scaled down fiddle here with the grid in the centre of the viewport.
JS Fiddle here
In the fiddle I have my main grid of coloured squares in the centre, I want them tiled infinitely in all directions. Obviously this isn't realistically possible, so I want to give the illusion of infinite by just redrawing the grids based on the scroll direction.
I found some good articles:
https://developer.mozilla.org/en-US/docs/Games/Techniques/Tilemaps/Square_tilemaps_implementation:_Scrolling_maps
https://gamedev.stackexchange.com/questions/71583/html5-dynamic-canvas-grid-for-scrolling-a-big-map
And the best route seems to be to get the drag direction and then reset camera to that point, so the layers scroll under the main canvas viewport, thus meaning the camera can never reach the edge of the main viewport canvas.
I've worked on adding some event listeners for mouse drags :
Fiddle with mouse events
var bMouseDown = false;
var oPreviousCoords = {
'x': 0,
'y': 0
}
var oDelta;
var oEndCoords;
var newLayerTop;
$(document).on('mousedown', function (oEvent) {
bMouseDown = true;
oPreviousCoords = {
'x': oEvent.pageX,
'y': oEvent.pageY
}
});
$(document).on('mouseup', function (oEvent) {
bMouseDown = false;
oPreviousCoords = {
'x': oEvent.pageX,
'y': oEvent.pageY
}
oEndCoords = oDelta
if(oEndCoords.y < -300){
if(newLayerTop){
newLayerTop.destroy();
}
layerCurentPosition = layer.position();
newLayerTop = layer.clone();
newLayerTop.position({
x: layerCurentPosition.x,
y: layerCurentPosition.y -1960
});
stage.add(newLayerTop)
stage.batchDraw();
}
});
$(document).on('mousemove', function (oEvent) {
if (!bMouseDown) {
return;
}
oDelta = {
'x': oPreviousCoords.x - oEvent.pageX,
'y': oPreviousCoords.y - oEvent.pageY
}
});
But I can't reliably work out the co-ordinates for each direction and then how to reset the camera position.
As you need "infinite" canvas I suggest to not use scrolling and make canvas as large as user viewport. Then you can emulate camera and on every move, you need to draw a new grid on the canvas. You just need to carefully calculate the position of the grid.
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
draggable: true
});
const layer = new Konva.Layer();
stage.add(layer);
const WIDTH = 100;
const HEIGHT = 100;
const grid = [
['red', 'yellow'],
['green', 'blue']
];
function checkShapes() {
const startX = Math.floor((-stage.x() - stage.width()) / WIDTH) * WIDTH;
const endX = Math.floor((-stage.x() + stage.width() * 2) / WIDTH) * WIDTH;
const startY = Math.floor((-stage.y() - stage.height()) / HEIGHT) * HEIGHT;
const endY = Math.floor((-stage.y() + stage.height() * 2) / HEIGHT) * HEIGHT;
for(var x = startX; x < endX; x += WIDTH) {
for(var y = startY; y < endY; y += HEIGHT) {
const indexX = Math.abs(x / WIDTH) % grid.length;
const indexY = Math.abs(y / HEIGHT) % grid[0].length;
layer.add(new Konva.Rect({
x,
y,
width: WIDTH,
height: HEIGHT,
fill: grid[indexX][indexY]
}))
}
}
}
checkShapes();
layer.draw();
stage.on('dragend', () => {
layer.destroyChildren();
checkShapes();
layer.draw();
})
<script src="https://unpkg.com/konva#^2/konva.min.js"></script>
<div id="container"></div>
If you need scrolling you can listen wheel event on stage and move into desired direction.
I need help only having the anchors for rotating. Right now there is five anchors and I don't know how to get rid of all of them except the rotate one. I would also only like the anchors to show when the user hovers over the image
Here is my code
<html>
<head>
<style>
body {
margin: 0px;
padding: 0px;
}
</style>
</head>
<body>
<body onmousedown="return false;">
<div id="container"></div>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v4.7.4.min.js">
</script>
<script>
function update(activeAnchor) {
var group = activeAnchor.getParent();
var topLeft = group.get('.topLeft')[0];
var topRight = group.get('.topRight')[0];
var bottomRight = group.get('.bottomRight')[0];
var bottomLeft = group.get('.bottomLeft')[0];
var rotateAnchor = group.get('.rotateAnchor')[0];
var image = group.get('Image')[0];
var anchorX = activeAnchor.getX();
var anchorY = activeAnchor.getY();
var imageWidth = image.getWidth();
var imageHeight = image.getHeight();
var offsetX = Math.abs((topLeft.getX() + bottomRight.getX() + 10) / 2);
var offsetY = Math.abs((topLeft.getY() + bottomRight.getY() + 10) / 2);
// update anchor positions
switch (activeAnchor.getName()) {
case 'rotateAnchor':
group.setOffset(offsetX, offsetY);
break;
case 'topLeft':
topRight.setY(anchorY);
bottomLeft.setX(anchorX);
break;
case 'topRight':
topLeft.setY(anchorY);
bottomRight.setX(anchorX);
break;
case 'bottomRight':
topRight.setX(anchorX);
bottomLeft.setY(anchorY);
break;
case 'bottomLeft':
topLeft.setX(anchorX);
bottomRight.setY(anchorY);
break;
}
rotateAnchor.setX(topRight.getX() + 5);
rotateAnchor.setY(topRight.getY() + 20);
image.setPosition((topLeft.getPosition().x + 20), (topLeft.getPosition().y + 20));
var width = topRight.getX() - topLeft.getX() - 30;
var height = bottomLeft.getY() - topLeft.getY() - 30;
if (width && height) {
image.setSize(width, height);
}
}
function addAnchor(group, x, y, name, dragBound) {
var stage = group.getStage();
var layer = group.getLayer();
var anchor = new Kinetic.Circle({
x: x,
y: y,
stroke: '#666',
fill: '#ddd',
strokeWidth: 2,
radius: 8,
name: name,
draggable: true,
dragOnTop: false
});
if (dragBound == 'rotate') {
anchor.setAttrs({
dragBoundFunc: function (pos) {
return getRotatingAnchorBounds(pos, group);
}
});
}
anchor.on('dragmove', function() {
update(this);
layer.draw();
});
anchor.on('mousedown touchstart', function() {
group.setDraggable(false);
this.moveToTop();
});
anchor.on('dragend', function() {
group.setDraggable(true);
layer.draw();
});
// add hover styling
anchor.on('mouseover', function() {
var layer = this.getLayer();
document.body.style.cursor = 'pointer';
this.setStrokeWidth(4);
layer.draw();
});
anchor.on('mouseout', function() {
var layer = this.getLayer();
document.body.style.cursor = 'default';
this.setStrokeWidth(2);
layer.draw();
});
group.add(anchor);
}
function loadImages(sources, callback) {
var images = {};
var loadedImages = 0;
var numImages = 0;
for(var src in sources) {
numImages++;
}
for(var src in sources) {
images[src] = new Image();
images[src].onload = function() {
if(++loadedImages >= numImages) {
callback(images);
}
};
images[src].src = sources[src];
}
}
function getRotatingAnchorBounds(pos, group) {
var topLeft = group.get('.topLeft')[0];
var bottomRight = group.get('.bottomRight')[0];
var topRight = group.get('.topRight')[0];
var absCenterX = Math.abs((topLeft.getAbsolutePosition().x + 5 + bottomRight.getAbsolutePosition().x + 5) / 2);
var absCenterY = Math.abs((topLeft.getAbsolutePosition().y + 5 + bottomRight.getAbsolutePosition().y + 5) / 2);
var relCenterX = Math.abs((topLeft.getX() + bottomRight.getX()) / 2);
var relCenterY = Math.abs((topLeft.getY() + bottomRight.getY()) / 2);
var radius = distance(relCenterX, relCenterY, topRight.getX() + 5, topRight.getY() + 20);
var scale = radius / distance(pos.x, pos.y, absCenterX, absCenterY);
var realRotation = Math.round(degrees(angle(relCenterX, relCenterY, topRight.getX() + 5, topRight.getY() + 20)));
var rotation = Math.round(degrees(angle(absCenterX, absCenterY, pos.x, pos.y)));
rotation -= realRotation;
group.setRotationDeg(rotation);
return {
y: Math.round((pos.y - absCenterY) * scale + absCenterY),
x: Math.round((pos.x - absCenterX) * scale + absCenterX)
};
}
function radians(degrees) { return degrees * (Math.PI / 180); }
function degrees(radians) { return radians * (180 / Math.PI); }
// Calculate the angle between two points.
function angle(cx, cy, px, py) {
var x = cx - px;
var y = cy - py;
return Math.atan2(-y, -x);
}
// Calculate the distance between two points.
function distance(p1x, p1y, p2x, p2y) {
return Math.sqrt(Math.pow((p2x - p1x), 2) + Math.pow((p2y - p1y), 2));
}
function initStage(images) {
var stage = new Kinetic.Stage({
container: 'container',
width: 578,
height: 400
});
var darthVaderGroup = new Kinetic.Group({
x: 270,
y: 100,
draggable: true
});
var yodaGroup = new Kinetic.Group({
x: 100,
y: 110,
draggable: true
});
var layer = new Kinetic.Layer();
/*
* go ahead and add the groups
* to the layer and the layer to the
* stage so that the groups have knowledge
* of its layer and stage
*/
layer.add(darthVaderGroup);
layer.add(yodaGroup);
stage.add(layer);
// darth vader
var darthVaderImg = new Kinetic.Image({
x: 0,
y: 0,
image: images.darthVader,
width: 200,
height: 138,
name: 'image'
});
darthVaderGroup.add(darthVaderImg);
addAnchor(darthVaderGroup, -20, -20, 'topLeft', 'none');
addAnchor(darthVaderGroup, 220, -20, 'topRight', 'none');
addAnchor(darthVaderGroup, 220, 158, 'bottomRight', 'none');
addAnchor(darthVaderGroup, -20, 158, 'bottomLeft','none');
addAnchor(darthVaderGroup, 225, 0, 'rotateAnchor','rotate');
darthVaderGroup.on('dragstart', function() {
this.moveToTop();
});
stage.draw();
}
var sources = {
darthVader: 'http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg'
};
loadImages(sources, initStage);
</script>
</body>
</html>
You can use each anchors show/hide methods inside the images mouseenter/mouseleave events to display the anchors when the mouse enters the image:
image.on("mouseleave",function(){ anchor1.hide(); }
image.on("mouseenter",function(){ anchor1.show(); layer.draw(); }
Problem is that since your anchors are partly outside your image, so hiding the anchors when the mouse leaves the image might make the anchors "disappear" when the user intends to use them.
The ideal solution would be to listen for mouseenter/mouseleave events on the group which contains the image but also extends to include the outside part of the anchors. Unfortunately, a Kinetic.Group will not respond to mouseenter/mouseleave events.
A workaround is to create a Kinetic.Rect background to the group which includes the images plus the anchors. The rect will listen for mouseenter/mouseleave events and will show/hide the anchors. If you don't want the background rect to be visible, just set it's opacity to .001. The rect will still listen for events, but will be invisible.
groupBackgroundRect.on("mouseleave",function(){ anchor1.hide(); }
groupBackgroundRect.on("mouseenter",function(){ anchor1.show(); layer.draw(); }
A related note:
With KineticJS, combining rotation with resizing is made more difficult than it needs to be because KineticJS uses offsetX/offsetY as both an object's rotation point and as an offset to its position. Your key to making it work will be to re-center the offset point after resizing so that your rotation takes place around the new centerpoint--not the previous centerpoint. (or reset the offset reference point to any other point that you want to rotate around).