How to make sure image alway include entire clipping Rect inside while it's transforming (move, scale) in fabric.js? - html5-canvas

I have a Image with a simple clipping Rect ( rotate 30 degree with size 200x200 to make a small view part of image ). My problem is how to prevent Image move or scale outside clipping rect ( that mean Image need alway include entire clipping Rect )?
Here is my code:
var canvasObject = document.getElementById("editorCanvas");
// set canvas equal size with div
$(canvasObject).width($("#canvasContainer").width());
$(canvasObject).height($("#canvasContainer").height());
var canvas = new fabric.Canvas('editorCanvas', {
backgroundColor: 'white',
selectionLineWidth: 2,
width: $("#canvasContainer").width(),
height: $("#canvasContainer").height()
});
// create a clipping path
var rectClippingPath = new fabric.Rect({
top: 100,
left: 200,
width: 200,
height: 200,
fill: 'red',
angle: 30,
absolutePositioned: true
});
// create image
var newImage = new fabric.Image();
newImage.clipPath = rectClippingPath;
canvas.add(newImage);
newImage.setSrc('http://fabricjs.com/assets/pug_small.jpg', (imageObject) => {
newImage.set({ left: 50, top: 50});
canvas.setActiveObject(newImage);
newImage.setCoords();
canvas.requestRenderAll();
})
#canvasContainer {
width: 100%;
height: 100vh;
background-color: gray;
}
<script src="https://code.jquery.com/jquery-3.3.1.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.6/fabric.min.js" integrity="sha512-XcwgBTqf2CXR/nsswCV1e0j9CjXo87APyBsATK2/l7MvTpcIG0QYKA87v5KIJ4RS6ytArv2pWD6UcRorKhYp1A==" crossorigin="anonymous"></script>
<div id="canvasContainer">
<canvas id="editorCanvas"></canvas>
</div>
And here are some example cases:
Here is expected result:
Thank you!

I can't see a way to achieve it based on your example. I think it needs a structural change. You can do it by having a parent element with all controls and then a child element (image object).
Whenever you move the parent frame you should force the photo to move by the same amount, whenever you scale the frame you should also proportionally scale the child photo, same with rotation.
Let's say you rotate the parent frame, now in order to manipulate the photo inside you will need some kind of activating trigger: double click or a button. That will make sure you are not moving the parent frame, but the child.
While you move the child photo you will need to force it to stay contained within the parent frame borders. This is not hard if both elements have a rotation of 0, 90, 180, 270, 360 degrees. It is much harder to make it nice and smooth for all other rotation angles. You can find a working, but not perfect solution here: Contain a rotated object inside another rotated object FabricJS. I also started working on a smoother solution for that, but it is not completed https://codesandbox.io/s/force-contain-of-object-inside-another-object-fabric-js-v3-j38sz.
Implementing this solution you will most probably encounter also an issue having with rotating. It's easy to rotate an child image and the parent if they have the same origin point, but if you move the child and try to rotate the parent, then you will need to rotate them considering a different origin point. Here is a solution to this: https://codesandbox.io/s/rotation-of-an-object-with-different-rotation-origin-fabric-js-362-8m4cb?file=/src/index.js
You can also visit this platform and have look at similar ideas https://diamondphoto.co.nz/. It is build in Fabric JS version 3.

Related

Image pile with position absolute

I've created a pile of images on which you click through. By clicking the top image it moves the z-index back by the amount of images. These images need to have a shared description. If I put position:absolute on them (to stack them). The description also gets stacked. Is there a way to keep them it under the images?
.image-pile {
position: relative;
}
.images img {
position: absolute;
width:300px;
}
https://jsfiddle.net/td59pr1h/
The .image-pile had no height because the content was positioned absolute. Fixed it by calculating the height in javascript.
var imgheight = $( ".images img" ).innerHeight()
$('.images').css('height', imgheight)

How to modify the shape of a control widget in FabricJS?

I'm using FabricJS in my web application. As I draw shapes, I notice that the control widget for all the objects are rectangular. Can the shape of a control widget be modified to fit the actual shape of the object? Say, a circle with a circular control widget.
I went through the Controls Customization demo. But there is no option to modify the shape of a control widget.
I need to modify the shape of the control widgets for the following purpose:
In my application whenever an object is clicked, its fill colour is toggled between black and white. In this case(refer the image below), because the circle has a rectangular control widget, clicking the pointed pixel selects the circle instead of the rectangle. I also hide the control widget from appearing by using hasControls property. So, the user will not know anything about the control widget. So, is the shape of the control widget editable?
I don't think there is an easy way to do that with fabric.js without writing a whole bunch of extra code. Fabric.js always draws a rectangle bounding box and uses it for selection controls. However, since you're planning on hiding the controls anyway, it sounds like you might be better off using perPixelTargetFind.
When true, object detection happens on per-pixel basis rather than on
per-bounding-box
Here's a quick demo:
const canvas = new fabric.Canvas('c', {
preserveObjectStacking: true,
perPixelTargetFind: true
})
const circle = new fabric.Circle({
radius: 50,
left: 50,
top: 50,
stroke:'green',
fill: 'white',
hasControls: false,
hasBorders: false
})
const rect = new fabric.Rect({
width: 200,
height: 150,
left: 50,
top: 25,
stroke: 'green',
fill: 'black',
hasControls: false,
hasBorders: false
})
canvas.on('mouse:down', (e) => {
const obj = e.target
if (obj) {
obj.set({
fill: obj.fill === 'white' ? 'black' : 'white'
})
canvas.requestRenderAll()
}
})
canvas.add(circle, rect)
circle.bringToFront()
canvas.requestRenderAll()
body {
background: ivory;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.4.0/fabric.min.js"></script>
<canvas id="c" width="300" height="200"></canvas>

how can I free drawing on shapes in konvajs

I want to make free drawing on top of shapes at konvajs. like an exp; Can u give me advise about shapes attrs like zindex or smt.
https://ibb.co/jq9pUK
Your question is very broad and you are not showing what you have tried so far. You would get better help faster if you give a clear description and post cut-down sample code for your questions.
Konvajs works on top of the HTML5 canvas. When working with a konvajs you put shapes, lines, images and text on to layers. Layers have a z-order and shapes on a layer have a z-order.
To answer your question, I would follow the pattern:
- create the stage
- create the shape layer
- add the shapes to the shape layer - triangles, rectangles, circles, etc
- add another layer for the freehand drawing
- draw on this layer.
Because of the sequence of adding the components to the canvas the z-order will support what you ask for in your question. If you wanted the drawing to happen 'behind' the shapes you would create the layers in the opposite sequence.
The working snippet below shows how to do the steps that I have listed above, and how to listen for the events you need to make it operate. You can extend from this starter code to handle erasing, selecting line colour, thickness, and stroke style. See the Konvajs drawing tutorial for more information.
Good luck.
// Set up the canvas / stage
var s1 = new Konva.Stage({container: 'container1', width: 300, height: 200});
// Add a layer for the shapes
var layer1 = new Konva.Layer({draggable: false});
s1.add(layer1);
// draw a cirlce
var circle = new Konva.Circle({
x: 80,
y: s1.getHeight() / 2,
radius: 70,
fill: 'red',
stroke: 'black',
strokeWidth: 4
})
layer1.add(circle)
// draw a wedge.
var wedge = new Konva.Wedge({
x: 200,
y: s1.getHeight() / 2,
radius: 70,
angle: 60,
fill: 'gold',
stroke: 'black',
strokeWidth: 4,
rotation: -120
});
layer1.add(wedge)
// Now add a layer for freehand drawing
var layer2 = new Konva.Layer({draggable: false});
s1.add(layer2);
// Add a rectangle to layer2 to catch events. Make it semi-transparent
var r = new Konva.Rect({x:0, y: 0, width: 300, height: 200, fill: 'blue', opacity: 0.1})
layer2.add(r)
// Everything is ready so draw the canvas objects set up so far.
s1.draw()
var drawingLine = null; // handle to the line we are drawing
var isPaint = false; // flag to indicate we are painting
// Listen for mouse down on the rectangle. When we get one, get a new line and set the initial point
r.on('mousedown touchstart', function () {
isPaint = true;
var pos = s1.getPointerPosition();
drawingLine = newLine(pos.x, pos.y);
drawingLine.points(drawingLine.points().concat(pos.x,pos.y));
layer2.draw();
});
// Listen for mouse up ON THE STAGE, because the mouseup will not fire on the rect because the mouse is actually over the line point we just drew when it is released.
s1.on('mouseup touchend', function () {
isPaint = false;
drawingLine = null;
});
// when the mouse is moved, add the position to the line points and refresh the layer to see the effect.
r.on('mousemove touchmove', function () {
if (!isPaint) {
return;
}
var pos = s1.getPointerPosition();
drawingLine.points(drawingLine.points().concat(pos.x,pos.y));
layer2.draw();
})
// Function to add and return a line object. We will extend this line to give the appearance of drawing.
function newLine(x,y){
var line = new Konva.Line({
points: [x,y,x,y],
stroke: 'limegreen',
strokeWidth: 4,
lineCap: 'round',
lineJoin: 'round'
});
layer2.add(line)
return line;
}
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 canvas to draw a line over the shapes.
</p>
<div id='container1' style="display: inline-block; width: 300px, height: 200px; background-color: silver; overflow: hidden; position: relative;"></div>

KineticJS rotating/animating text

I have this fiddle where I have text arranged in a circle, I would like now to animate it and rotate the text in a clockwise/counter clockwise motion.
Every animation demo I have seen uses a container as the starting point however all the examples i could find about manipulating text in a circular arrangement have all started with the element. I have tried 100's of variations trying to get this working but I am either missing something or it's not possible with the construction i have used thus far.
Here is the fiddle for the circular text I have so far:
http://jsfiddle.net/jamesburt/Sa2G8/
<canvas id="canvas1" width="500" height="500"></canvas>
var can = document.getElementById('canvas1');
var ctx = can.getContext('2d');
Where as the animation examples start off with:
<div id="container"></div>
var stage = new Kinetic.Stage({container: 'container'});
I'm open to any ideas / rewrites needed as ultimately my goal is an animated text circle.
Also if this is easily accomplished in an alternative to KineticJS I'd be interested in trying that out.
Here is a demo I made using KineticJS: http://jsfiddle.net/Moonseeker/Xf7hp/
var stage = new Kinetic.Stage({
container: 'container',
width: 500,
height: 500
});
var layer = new Kinetic.Layer();
var myText = "My text in a circle. ";
var centerCoords = {x:250, y:250};
for(var i=0;i<myText.length;i++){
var rotation = i*360/myText.length;
var oneChar = new Kinetic.Text({
x: centerCoords.x,
y: centerCoords.y,
text: myText[i],
fontSize: 30,
fontFamily: 'Calibri',
fill: 'green',
offset: {x:0, y:100},
rotationDeg: rotation
});
layer.add(oneChar);
}
// add the layer to the stage
stage.add(layer);
var angularSpeed = Math.PI / 2;
var anim = new Kinetic.Animation(function(frame){
var angleDiff = frame.timeDiff * angularSpeed / 1000;
for(var i=0;i<layer.children.length;i++){
layer.children[i].rotate(angleDiff);
};
}, layer);
anim.start();
You can rotate at every direction or speed you wish, you can change the style of the circle.
You should be able to use layer.find('Text').each() instead of the for-loop for looping through the text to rotate but the KineticJS library at jsfiddle seems outdated as it doesn't know the find method.
One efficient way:
Render your text-around-a-circle on an offscreen canvas.
Save that offscreen canvas as an image using .toDataURL
Create a Kinetic.Image from that offscreen image.
You can then efficiently rotate/animate the Kinetic.Image as you need.

Fixed floating shape on kinetic.js stage

I have tried to find a solution on how to implement fixed score table to visible stage (canvas) area by using kineticjs library layers and stages. The basic idea is that shape layer can be scrolled and stage is wider than the visible browser area but the score table should be all the time fixed to certain position on the visible area like in many casual games.
What would be the best approach on creating this kind of fixed always visible shapes to stage/canvas? Is there a way to refer to visible area xy position?
Your best bet for making a fixed position group is either to use a second layer on the stage or to create two groups for your objects - one that remains in place and another that can be moved. I'd go with the dual-layer approach as this allows you to redraw the layers separately so that when one has updated information, you don't need to redraw the other.
cvsObj.page = new Kinetic.Layer();
cvsObj.dynamic = new Kinetic.Layer();
cvsObj.stage.add(cvsObj.page);
cvsObj.stage.add(cvsObj.dynamic);
I took the two layer approach but actually my challenge was how to implement 'page' static behavior. I figured out that as it seems that Kinetic layers/stages do not offer methods for this I need to use other means to make 'page' layer on top of viewable area. I used jquery scrollTop() and scrollLeft()functions for this. And layer method .setAbsolutePosition() is used to update position for whole layer. Here pageLayer position is updated and dynamicLayer not so naming might be bit confusing but reflects what you see on browser.
Here is a simple example what does what I was looking for. Now the green rectangle moves when you scroll stage and the red rectangle and the x/y position counter stay on visible area. I attach this solution here with tutorial type of content as it might be useful.
<script>
window.onload = function() {
var stage = new Kinetic.Stage({
container: 'container',
width: 1600, height: 1600
});
var layerDynamic = new Kinetic.Layer();
var layerPage = new Kinetic.Layer();
var rectGreen = new Kinetic.Rect({
x: 239, y: 75, width: 100, height: 50, fill: 'green'
});
var rectRed = new Kinetic.Rect({
x: 100, y: 75, width: 100, height: 50, fill: 'red'
});
var simpleText = new Kinetic.Text({
x: 1, y: 1, text: "Init", textFill: "black"
});
layerDynamic.add(rectGreen);
layerPage.add(rectRed);
layerPage.add(simpleText);
$(document).scroll(function(e) {
var visibleXTop = $(this).scrollTop();
var visibleYTop = $(this).scrollLeft();
simpleText.setAttrs({text: visibleXTop + ' ' + visibleYTop});
layerPage.setAbsolutePosition(visibleYTop , visibleXTop);
layerPage.draw();
});
stage.add(layerDynamic);
stage.add(layerPage);
};
</script>

Resources