KineticJS Image Sprite as toolbar button images in canvas - html

I am trying to create a toolbar in canvas (no div elements) using KineticJS. i have an image sprite which consists of 10 images, i need to map the individual sprite image as a button in a toolbar. How to do this using KineticJS.Sprite.
EDIT:
the events are not handled properly( i get subscription from the last sprite index (i.e 7), should i create separate objects for storing each sprite events).?
EDIT 2:
Links Removed

After going through the Kinetic JS code base I have finally found a solution for the above problem, the final implementation is shared below:
var buttons = {
button: [{
//button1
x: 0,
y: 0,
width: 28,
height: 28
}, {
//button2
x: 29,
y: 0,
width: 28,
height: 28
}, {
//button3
x: 58,
y: 0,
width: 28,
height: 28
}]};
var imageObj = new Image();
imageObj.onload = function () {
for (var i = 0; i < 12; i++) {
var blob = new Kinetic.Sprite({
x: 50 + i * 30,
y: 40,
name: i,
image: imageObj,
index: i,
animation: 'button',
animations: buttons
});
toolbarbuttonGroup.add(blob);
}
toolbarbuttonGroup.on('click', function (evt) {
var buttonId= evt.shape;
alert('Clicked on \"' + buttonId.getName() + '\"');
});

Related

How can i slide an image on canvas with mouse move?

I am showing a part (300px*300px) of an image (1200px*1200px) with drawimage function of html5 in react. However i want the picture to slide in the canvas and show more of it when i move my mouse to the left/right sides of the canvas. So i wrote some code to do this like below
import React from 'react';
import { useRef,useEffect } from 'react';
import Space from "../img/space.jpg";
let canvas;
let ctx;
let ref;
let img_world;
let x = 0;
let y = 0;
let pixels = 30;
let frames =1000/30;
function onMouseMove(e) {
x = window.scrollX+e.clientX - (window.scrollX + canvas.getBoundingClientRect().left);
y = window.scrollY+e.clientY - (window.scrollY + canvas.getBoundingClientRect().top);
if(x<10 || x>290){
moveCanvas();
}
pixels=30;
}
function moveCanvas(){
if(pixels===350){
return;
}
if(x<10){
ctx.clearRect(0, 0, 300, 300);
ctx.drawImage(img_world, 350-pixels, 250, 300, 300, 0, 0, 300, 300);
console.log("to the left side: "+pixels);
}else if(x>90){
ctx.clearRect(0, 0, 300, 300);
ctx.drawImage(img_world, 350+pixels, 250, 300, 300, 0, 0, 300, 300);
console.log("to the right side: "+pixels);
}
pixels = pixels + 1;
setTimeout(moveCanvas(), frames);
}
function World() {
ref = useRef();
useEffect(() => {
img_world = new Image();
img_world.src = Space;
canvas = ref.current;
ctx = canvas.getContext('2d');
img_world.onload = function() {
ctx.drawImage(img_world, 350, 250, 300, 300, 0, 0, 300, 300);
};
canvas.addEventListener("mousemove", onMouseMove, false);
//canvas.addEventListener("mouseover", onMouse, false);
return () => {
canvas.removeEventListener("mousemove", onMouseMove);
};
},[]);
return (
<div className="screens">
<canvas
ref={ref}
className="world"
width={300}
height={300}
/>
</div>
);
}
export default World;
So as you can see from my code, when a mouse movement happens inside the canvas from left and right sides, it should trigger recursive mouseCanvas() function and it does then it supposed to drawimages 30 times a second until pixels variable reachs 350 but in browser i see only a movement happening once and not by 1 pixel. It seems like it is drawing the image once when pixels variable reach limit 350. What am i doing wrong here.
Thanks inadvance.

Draw rectangle with mouse and fill with color on mouseup

I am using Konva library for drawing a canvas and dragging images onto it
My question is how can I select a part of canvas and fill color to the selected portion using Konva
1) users select a portion by dragging mouse
2) fill color what he wants to the selected portion
The only way to have part of the canvas filled is to use a shape.
This snippet should get you going.
To select the fill color you will need to open some kind of custom color selection process in the mouseup event.
// Set up the canvas and shapes
var s1 = new Konva.Stage({container: 'container1', width: 300, height: 200});
var layer1 = new Konva.Layer({draggable: false});
s1.add(layer1);
// draw a background rect to catch events.
var r1 = new Konva.Rect({x: 0, y: 0, width: 300, height: 200, fill: 'gold' })
layer1.add(r1)
// draw a rectangle to be used as the rubber area
var r2 = new Konva.Rect({x: 0, y: 0, width: 0, height: 0, stroke: 'red', dash: [2,2]})
r2.listening(false); // stop r2 catching our mouse events.
layer1.add(r2)
s1.draw() // First draw of canvas.
var posStart;
var posNow;
var mode = '';
function startDrag(posIn){
posStart = {x: posIn.x, y: posIn.y};
posNow = {x: posIn.x, y: posIn.y};
}
function updateDrag(posIn){
// update rubber rect position
posNow = {x: posIn.x, y: posIn.y};
var posRect = reverse(posStart,posNow);
r2.x(posRect.x1);
r2.y(posRect.y1);
r2.width(posRect.x2 - posRect.x1);
r2.height(posRect.y2 - posRect.y1);
r2.visible(true);
s1.draw(); // redraw any changes.
}
// start the rubber drawing on mouse down.
r1.on('mousedown', function(e){
mode = 'drawing';
startDrag({x: e.evt.layerX, y: e.evt.layerY})
})
// update the rubber rect on mouse move - note use of 'mode' var to avoid drawing after mouse released.
r1.on('mousemove', function(e){
if (mode === 'drawing'){
updateDrag({x: e.evt.layerX, y: e.evt.layerY})
}
})
// here we create the new rect using the location and dimensions of the drawing rect.
r1.on('mouseup', function(e){
mode = '';
r2.visible(false);
var newRect = new Konva.Rect({
x: r2.x(),
y: r2.y(),
width: r2.width(),
height: r2.height(),
fill: 'red',
listening: false
})
layer1.add(newRect);
s1.draw();
})
// reverse co-ords if user drags left / up
function reverse(r1, r2){
var r1x = r1.x, r1y = r1.y, r2x = r2.x, r2y = r2.y, d;
if (r1x > r2x ){
d = Math.abs(r1x - r2x);
r1x = r2x; r2x = r1x + d;
}
if (r1y > r2y ){
d = Math.abs(r1y - r2y);
r1y = r2y; r2y = r1y + d;
}
return ({x1: r1x, y1: r1y, x2: r2x, y2: r2y}); // return the corrected rect.
}
p
{
padding: 4px;
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<script src="https://cdn.rawgit.com/konvajs/konva/1.6.5/konva.min.js"></script>
<p>Click & drag on the background to draw a rectangle and fill it.
</p>
<div id='container1' style="display: inline-block; width: 300px, height: 200px; background-color: silver; overflow: hidden; position: relative;"></div>

fabricJS text with uniform lineheight for each line

I want the text lines to exactly have the same lineHeights but the first line doesn't have the line height
Please check this fiddle
https://jsfiddle.net/fahadnabbasi/zukc6pvc/1/
var canvas = new fabric.Canvas('c');
var text_temp = new fabric.IText("One Two Three\nFour Five Six\nSeven Eight Nine", {
left: 20,
top: 20,
fontSize: 48,
fill: "#000000",
lineHeight : 2
});
canvas.add(text_temp)
I want it something like this attached image
var canvas = new fabric.Canvas('c');
// override this to get always full line height
fabric.Text.prototype.calcTextHeight = function() {
var lineHeight, height = 0;
for (var i = 0, len = this._textLines.length; i < len; i++) {
lineHeight = this.getHeightOfLine(i);
height += lineHeight;
}
return height;
};
fabric.Text.prototype._getTopOffset = function() {
return -this.height / 2 + this.getHeightOfLine(0) / this.lineHeight;
};
var text_temp = new fabric.IText("One Two Three\nFour Five Six\nSeven Eight Nine", {
left: 20,
top: 20,
fontSize: 48,
textBackgroundColor: 'red',
backgroundColor: 'yellow',
fill: "#000000",
lineHeight : 2
});
canvas.add(text_temp)
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id="c" width="600" height="600"></canvas>
I'm not sure this is a complete solution, nor i want to go deeper in modifying a core behaviour of the library. This snippet can helpyou but can also fail in more advanced use cases.

How to make KineticJS canvas stage responsive

When adding a canvas element to a page I want the canvas and its contents to scale down as the browser window scales down.
There seem to be several questions asked about this here and with each one I haven't quite found the answer I was looking for. I seem to have something that works and thought I would post it for others to shoot down or improve upon.
In this example I have a page which is 1000px wide at max width. The canvas element is only 800px wide at max width.
HTML:
<div id="container"></div>
CSS:
#container {
background-color: #888888;
display: inline-block;
max-width: 1000px;
}
JavaScript:
var maxStageWidth = 800;
var maxStageHeight = 500;
var maxPageWidth = 1000;
// Check to see if window is less than desired width and calls sizing functions
function setStageWidth() {
if (window.innerWidth < maxPageWidth) {
resizeStage();
} else {
maxStageSize();
};
};
// Sets scale and dimensions of stage in relation to window size
function resizeStage() {
var scalePercentage = window.innerWidth / maxPageWidth;
stage.setAttr('scaleX', scalePercentage);
stage.setAttr('scaleY', scalePercentage);
stage.setAttr('width', maxStageWidth * scalePercentage);
stage.setAttr('height', maxStageHeight * scalePercentage);
stage.draw();
};
//Sets scale and dimensions of stage to max settings
function maxStageSize() {
stage.setAttr('scaleX', 1);
stage.setAttr('scaleY', 1);
stage.setAttr('width', maxStageWidth);
stage.setAttr('height', maxStageHeight);
stage.draw();
};
var stage = new Kinetic.Stage({
container: 'container',
width: maxStageWidth,
height: maxStageHeight,
scaleX: 1
});
var circles = new Kinetic.Layer();
var circleRed = new Kinetic.Circle({
x: stage.getWidth() / 2,
y: stage.getHeight() / 2,
radius: 100,
stroke: 'black',
strokeWidth: 2,
fill: 'red'
});
var circleBlue = new Kinetic.Circle({
x: stage.getWidth() / 2 + 120,
y: stage.getHeight() / 2 + 175,
radius: 50,
stroke: 'black',
strokeWidth: 2,
fill: 'blue'
});
var circleOrange = new Kinetic.Circle({
x: stage.getWidth() / 2 - 175,
y: stage.getHeight() / 2 + 100,
radius: 75,
stroke: 'black',
strokeWidth: 2,
fill: 'orange'
});
circles.add(circleRed);
circles.add(circleBlue);
circles.add(circleOrange);
stage.add(circles);
// On load we set stage size based on window size
setStageWidth();
// On window resize we resize the stage size
window.addEventListener('resize', setStageWidth);
There doesn't seem to be anything wrong with your functions. Can you explain more of what you're trying to acheive? Shouldn't your Max page width and max stage width be the same number?
Your functions are doing two things: scaling and resizing, but you are focusing on the scale based on the horizontal change. Try changing it to a diagonal scaling factor or scale vertical and horizontal separately.
// Sets scale and dimensions of stage in relation to window size
function resizeStage() {
var horizScalePercentage = window.innerWidth / maxPageWidth;
var vertiScalePercentage = window.innerHeight/ maxPageHeight;
stage.setAttr('scaleX', horizScalePercentage );
stage.setAttr('scaleY', vertiScalePercentage );
stage.setAttr('width', maxStageWidth * horizScalePercentage );
stage.setAttr('height', maxStageHeight * vertiScalePercentage );
stage.draw();
};
//Sets scale and dimensions of stage to max settings
function maxStageSize() {
stage.setAttr('scaleX', 1);
stage.setAttr('scaleY', 1);
stage.setAttr('width', maxStageWidth);
stage.setAttr('height', maxStageHeight);
stage.draw();
};
http://jsfiddle.net/8cw7J/1/ Check this fiddle

Kinect JS cropping rectangle

I am already using jQuery plugin called jCrop but recently I discovered KinectJs and it really solves many problems for me. Then I stumbled upon this example:
http://www.html5canvastutorials.com/labs/html5-canvas-drag-and-drop-resize-and-invert-images/
And I decided to write my own cropping rectangle based on KinectJs and above example.
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 cropper = group.get('.cropper')[0];
var leftMask = group.getParent().get('.leftMask')[0];
var rightMask = group.getParent().get('.rightMask')[0];
var topMask = group.getParent().get('.topMask')[0];
var bottomMask = group.getParent().get('.bottomMask')[0];
var anchorX = activeAnchor.getX();
var anchorY = activeAnchor.getY();
// update anchor positions
switch (activeAnchor.getName()) {
case 'topLeft':
topRight.setY(anchorY);
bottomLeft.setX(anchorX);
updateLeftMaskWidth(leftMask,activeAnchor);
updateTopMaskHeight(topMask,cropper,activeAnchor);
break;
case 'topRight':
topLeft.setY(anchorY);
bottomRight.setX(anchorX);
updateRightMaskWidthAndPos(rightMask,activeAnchor);
updateTopMaskHeight(topMask,cropper,activeAnchor);
break;
case 'bottomRight':
bottomLeft.setY(anchorY);
topRight.setX(anchorX);
updateRightMaskWidthAndPos(rightMask,activeAnchor);
updateBottomMaskHeightAndPos(bottomMask,cropper,activeAnchor);
break;
case 'bottomLeft':
bottomRight.setY(anchorY);
topLeft.setX(anchorX);
updateLeftMaskWidth(leftMask,activeAnchor);
updateBottomMaskHeightAndPos(bottomMask,cropper,activeAnchor);
break;
}
cropper.setPosition(topLeft.getPosition().x,topLeft.getPosition().y);
var width = topRight.getX() - topLeft.getX();
var height = bottomLeft.getY() - topLeft.getY();
if(width && height) {
cropper.setSize(width, height);
}
}
function updateLeftMaskWidth(mask,leftAnchor) {
mask.setWidth(leftAnchor.getAbsolutePosition().x - 100);
}
function updateRightMaskWidthAndPos(mask,rightAnchor) {
mask.setAbsolutePosition(rightAnchor.getAbsolutePosition().x,mask.getAbsolutePosition().y);
mask.setWidth(213 - (rightAnchor.getAbsolutePosition().x - 100));
}
function updateTopMaskHeight(mask,cropper,topAnchor) {
mask.setAbsolutePosition(topAnchor.getAbsolutePosition().x,mask.getAbsolutePosition().y);
mask.setHeight(topAnchor.getAbsolutePosition().y - 110);
mask.setWidth(cropper.getWidth());
}
function updateBottomMaskHeightAndPos(mask,cropper,bottomAnchor) {
mask.setAbsolutePosition(bottomAnchor.getAbsolutePosition().x, bottomAnchor.getAbsolutePosition().y);
mask.setHeight(236 - (bottomAnchor.getAbsolutePosition().y - 110));
mask.setWidth(cropper.getWidth());
}
function addAnchor(group, x, y, name) {
var stage = group.getStage();
var layer = group.getLayer();
var anchor = new Kinetic.Circle({
x: x,
y: y,
stroke: '#666',
fill: '#ddd',
strokeWidth: 1,
radius: 5,
name: name,
draggable: true,
dragBoundFunc: function(pos) {
var newX = pos.x;
var newY = pos.y;
var image = this.getParent().getParent().get('.image')[0];
var cropper = this.getParent();
// Bound horizontally
if(newX < 100) {
newX = 100;
}
else if(newX > image.getWidth() + 100 - cropper.getWidth()) {
newX = image.getWidth() + 100 - cropper.getWidth();
}
if(newY < 110) {
newY = 110;
}
else if(newY > image.getHeight() + 110 - cropper.getHeight()) {
newY = image.getHeight() + 110 - cropper.getHeight();
}
return {
x: newX,
y: newY
}
}
});
anchor.on('dragmove', function() {
update(this);
layer.draw();
});
// add hover styling
anchor.on('mouseover', function() {
var layer = this.getLayer();
document.body.style.cursor = 'pointer';
this.setStrokeWidth(2);
layer.draw();
});
anchor.on('mouseout', function() {
var layer = this.getLayer();
document.body.style.cursor = 'default';
this.setStrokeWidth(2);
layer.draw();
});
group.add(anchor);
}
function initStage(img) {
var stage = new Kinetic.Stage({
container: 'container',
width: 578,
height: 400
});
var imageGroup = new Kinetic.Group({
x: 100,
y: 110
});
var leftMaskGroup = new Kinetic.Group({
x: 100,
y: 110
});
var rightMaskGroup = new Kinetic.Group({
x: 270,
y: 110
});
var topMaskGroup = new Kinetic.Group({
x: 169.9,
y: 110
});
var bottomMaskGroup = new Kinetic.Group({
x: 169.9,
y: 150+138
});
var cropperGroup = new Kinetic.Group({
x: 170,
y: 150,
draggable: true,
dragBoundFunc: function(pos) {
var newX = pos.x;
var newY = pos.y;
var image = this.getParent().get('.image')[0];
var cropper = this.get('.cropper')[0];
// Bound horizontally
if(newX < 100) {
newX = 100;
}
else if(newX > image.getWidth() + 100 - cropper.getWidth()) {
newX = image.getWidth() + 100 - cropper.getWidth();
}
// Bound vertically
if(newY < 110) {
newY = 110;
}
else if(newY > image.getHeight() + 110 - cropper.getHeight()) {
newY = image.getHeight() + 110 - cropper.getHeight();
}
return {
x: newX,
y: newY
}
}
});
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(imageGroup);
layer.add(leftMaskGroup);
layer.add(rightMaskGroup);
layer.add(topMaskGroup);
layer.add(bottomMaskGroup);
layer.add(cropperGroup);
stage.add(layer);
// cropping rectangle
var cropperRect = new Kinetic.Rect({
x: 0,
y: 0,
width: 100,
height: 138,
stroke: 'black',
name: 'cropper',
strokeWidth: 1
});
cropperGroup.add(cropperRect);
addAnchor(cropperGroup, 0, 0, 'topLeft');
addAnchor(cropperGroup, 100, 0, 'topRight');
addAnchor(cropperGroup, 100, 138, 'bottomRight');
addAnchor(cropperGroup, 0, 138, 'bottomLeft');
cropperGroup.on('dragstart', function() {
this.moveToTop();
});
cropperGroup.on('dragmove', function() {
var layer = this.getLayer();
var topLeft = this.get('.topLeft')[0];
var bottomLeft = this.get('.bottomLeft')[0];
var topRight = this.get('.topRight')[0];
var leftMask = this.getParent().get('.leftMask')[0];
var rightMask = this.getParent().get('.rightMask')[0];
var topMask = this.getParent().get('.topMask')[0];
var bottomMask = this.getParent().get('.bottomMask')[0];
updateLeftMaskWidth(leftMask,topLeft);
updateRightMaskWidthAndPos(rightMask,topRight);
updateTopMaskHeight(topMask,this.get('.cropper')[0],topLeft);
updateBottomMaskHeightAndPos(bottomMask,this.get('.cropper')[0],bottomLeft);
layer.draw();
});
// left mask
var leftMaskRect = new Kinetic.Rect({
x: 0,
y: 0,
width: 70,
height: 236,
fill: 'black',
name: 'leftMask',
strokeWidth: 0,
opacity: 0.5
});
leftMaskGroup.add(leftMaskRect);
// right mask
var rightMaskRect = new Kinetic.Rect({
x: 0,
y: 0,
width: 213-170,
height: 236,
fill: 'black',
name: 'rightMask',
strokeWidth: 0,
opacity: 0.5
});
rightMaskGroup.add(rightMaskRect);
// top mask
var topMaskRect = new Kinetic.Rect({
x: 0,
y: 0,
width: 100.2,
height: 150-110,
fill: 'black',
name: 'topMask',
strokeWidth: 0,
opacity: 0.5
});
topMaskGroup.add(topMaskRect);
// bottom mask
var bottomMaskRect = new Kinetic.Rect({
x: 0,
y: 0,
width: 100.2,
height: 236-138-(150-110),
fill: 'black',
name: 'bottomMask',
strokeWidth: 0,
opacity: 0.5
});
bottomMaskGroup.add(bottomMaskRect);
// image
var srcImg = new Kinetic.Image({
x: 0,
y: 0,
image: img,
name: 'image'
});
imageGroup.add(srcImg);
stage.draw();
}
var img = new Image();
img.onload = function() {
initStage(this);
}
img.src = 'http://www.html5canvastutorials.com/demos/assets/yoda.jpg';
It works almost perfectly. The problem is that when you resize using the circle anchors and after a few tries (just give it a few shots) when you try to drag the whole rectangle it allows you to drag it out of bounds !
From my debugging this seems like an issue with the library but if sb. sees the problem in my code or sees a way to optimize it please share your thoughts.
The result of my effort can be seen here:
http://jsfiddle.net/wanderer/WLpXF/
A) Reproducing the bug:
Drag a top anchor upwards (say it was dragged N pixels)
Drag the entire cropper upwards; it can leave the image boundaries by N pixels
Similar behaviour for the other anchors and directions.
B) The solution:
Add this function to the script:
function readjust() {
var group = this.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 cropper = group.get('.cropper')[0];
var tlx = cropper.getX(),
tly = cropper.getY(),
w = cropper.getWidth(),
h = cropper.getHeight();
group.setX(group.getX() + tlx);
group.setY(group.getY() + tly);
topLeft.setPosition(0,0);
topRight.setPosition(w,0);
bottomLeft.setPosition(0,h);
bottomRight.setPosition(w,h);
cropper.setPosition(0,0);
}
And the following handler in addAnchor():
anchor.on('dragend', readjust);
Fiddle: http://jsfiddle.net/BMy7b/1/
Alternatively the code from readjust() can be included in update(), as suggested by MarmiK. This would run more operations on every drag thus might be slower (but I am not sure; opinions?). Fiddle: http://jsfiddle.net/vUPeQ/1/
The problem was that the cropper and anchors moved relatively to the cropperGroup, but the cropperGroup drag bounding function wasn't taking it into account.
Have fun!
One quick update: I was after something like this (nice looking cropping rectangle), but when I try this code with the latest KineticJS 5.0.1, well, it does a couple of nasty things. You can see by yourselves here: [http://jsfiddle.net/vUPeQ/2/]:http://jsfiddle.net/vUPeQ/2/
My wild guess is that it's because some api changes, but I can't find which one...
Can anyone give a hand over here?
Thanks so much!!!