svg, keep text from upside down - svg

I have a path in svg, it's original point is on the right, the following points are on the left, then I add a textPath to svg, all text are upside down.
How can I keep the text upright and always show from left to right?
This cost me a lot of time!
I'll appreciate your answer!
<!DOCTYPE html>
<html>
<head>
<style>
</style>
<script>
function CreateSVG(points, container) {
var id = "idSVG";
var ns = "http://www.w3.org/2000/svg";
var xlinkns = "http://www.w3.org/1999/xlink";
var lineWidth = 10;
var color = "rgba(120,120,120,0.7)";
var path = document.createElementNS(ns, "path");
path.setAttribute('stroke-linecap', 'round');
path.setAttribute('stroke-linejoin', 'round');
path.setAttribute('stroke-dasharray', 'solid');
path.setAttribute('fill', 'none');
path.setAttribute('id', id);
var _canvas = document.createElementNS(ns, 'svg');
_canvas.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xlink', xlinkns);
_canvas.setAttribute('pointer-events', 'none');
//this._canvas.appendChild(this.path);
var defs = document.createElementNS(ns, "defs");
defs.appendChild(path);
var useSvg = document.createElementNS(ns, 'use');
useSvg.setAttributeNS(xlinkns, 'xlink:href', '#' + id);
useSvg.setAttribute('fill', 'none');
useSvg.setAttribute('stroke', 'red');
var textSvg = document.createElementNS(ns, 'text');
var textPathSvg = document.createElementNS(ns, 'textPath');
textPathSvg.setAttributeNS(xlinkns, 'xlink:href', '#' + id);
textPathSvg.appendChild(document.createTextNode('test text'));
textSvg.appendChild(textPathSvg);
_canvas.appendChild(defs);
_canvas.appendChild(useSvg);
_canvas.appendChild(textSvg);
var minX, maxX, minY, maxY, x, y, width, height;
for(var i = 0, len = points.length; i < len; i++) {
x = points[i].x;
y = points[i].y;
if(minX == undefined || minX > x) {
minX = x;
}
if(maxX == undefined || maxX < x) {
maxX = x;
}
if(minY == undefined || minY > y) {
minY = y;
}
if(maxY == undefined || maxY < y) {
maxY = y;
}
}
minX = minX - lineWidth;
minY = minY - lineWidth;
width = maxX - minX + lineWidth;
height = maxY - minY + lineWidth;
_canvas.setAttribute('style', 'position:absolute;');
_canvas.setAttribute('width', width);
_canvas.setAttribute('height', height);
_canvas.setAttribute('viewBox', '0 0 ' + width + ' ' + height);
_canvas.setAttribute('stroke', 'true');
path.setAttribute('stroke-width', lineWidth);
path.setAttribute('stroke', color);
var d = [];
for(var i = 0, len = points.length; i < len; i++) {
d[i] = (points[i].x - minX + lineWidth / 2) + ',' + (points[i].y - minY + lineWidth / 2);
}
path.setAttribute('d', 'M' + d.join(' '));
container.appendChild(_canvas);
}
</script>
</head>
<body onload="CreateSVG([{x:200, y:200}, {x:40, y:60}], document.getElementById('svgContainer'))">
<div id="svgContainer"></div>
</body>
</html>

Related

d3.js - draw ellipse shape with 4 points by svg path

I would like to draw a ellipse shape by svg path. I use below code but the shape looks like a round corner rectangle.
How can I adjust the parameter to get a eclipse path!
demo()
function demo() {
var width = 400
var height = 200
var margin = {left:20,top:20,right:20,bottom:20}
var svg = d3.select('body').append("svg")
.attr('width',width + margin.left + margin.right)
.attr('height',height + margin.top + margin.bottom)
.style('border','2px solid red')
var g = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`)
var data = [[0,0,400,100]]
g.selectAll('node')
.data(data)
.enter()
.append('path')
.attr('class','node')
.attr('stroke','black')
.attr('stroke-width',2)
.attr('fill','none')
.attr('d',function(d) {
var w = d[2]
var h = d[3]
var ax = d[0]
var ay = d[1] + h/2
var bx = ax + w/2
var by = ay + h/2
var cx = ax + w
var cy = ay
var dx = bx
var dy = d[1]
add_dot(g,[[ax,ay],[bx,by],[cx,cy],[dx,dy]])
var path = ['M',ax,ay,'C',ax,by,ax,by,bx,by,
cx,by,cx,by,cx,cy,
cx,dy,cx,dy,dx,dy,
ax,dy,ax,dy,ax,ay]
return path.join(' ')
})
function add_dot(g,data) {
g.selectAll('dots')
.data(data)
.join('circle')
.attr('class','dots')
.attr('cx',d => d[0])
.attr('cy',d => d[1])
.attr('r',5)
.attr('fill','red')
}
}
<script src="https://d3js.org/d3.v6.min.js"></script>
The path C command draws a cubic bezier segment, which can approximate an ellipse, but is not exact. The A command draws an arc segment, a sector of an ellipse.
demo()
function demo() {
var width = 400
var height = 200
var margin = {left:20,top:20,right:20,bottom:20}
var svg = d3.select('body').append("svg")
.attr('width',width + margin.left + margin.right)
.attr('height',height + margin.top + margin.bottom)
.style('border','2px solid red')
var g = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`)
var data = [[0,0,400,100]]
g.selectAll('node')
.data(data)
.enter()
.append('path')
.attr('class','node')
.attr('stroke','black')
.attr('stroke-width',2)
.attr('fill','none')
.attr('d',function(d) {
var w = d[2]/2
var h = d[3]/2
var ax = d[0]
var ay = d[1] + h
var bx = ax + w
var by = ay + h
var cx = ax + w*2
var cy = ay
var dx = bx
var dy = d[1]
add_dot(g,[[ax,ay],[bx,by],[cx,cy],[dx,dy]])
var path = ['M',ax,ay,'A',w,h,0,0,0,bx,by,
w,h,0,0,0,cx,cy,
w,h,0,0,0,dx,dy,
w,h,0,0,0,ax,ay]
return path.join(' ')
})
function add_dot(g,data) {
g.selectAll('dots')
.data(data)
.join('circle')
.attr('class','dots')
.attr('cx',d => d[0])
.attr('cy',d => d[1])
.attr('r',5)
.attr('fill','red')
}
}
<script src="https://d3js.org/d3.v6.min.js"></script>
demo()
function demo() {
var width = 400
var height = 200
var margin = {left:20,top:20,right:20,bottom:20}
var svg = d3.select('body').append("svg")
.attr('width',width + margin.left + margin.right)
.attr('height',height + margin.top + margin.bottom)
.style('border','2px solid red')
var g = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`)
var data = [[0,0,400,100]]
g.selectAll('node')
.data(data)
.enter()
.append('path')
.attr('class','node')
.attr('stroke','black')
.attr('stroke-width',2)
.attr('fill','none')
.attr('d',function(d) {
var w = d[2]
var h = d[3]
var ax = d[0]
var ay = d[1] + h/2
var bx = ax + w/2
var by = ay + h/2
var cx = ax + w
var cy = ay
var dx = bx
var dy = d[1]
add_dot(g,[[ax,ay],[bx,by],[cx,cy],[dx,dy]])
var path = ['M',ax,ay,'C',ax,(ay+by)/2,(ax+bx)/2,by,bx,by,
(bx+cx)/2,by,cx,(by+cy)/2,cx,cy,
cx,(cy+dy)/2,(cx+dx)/2,dy,dx,dy,
(dx+ax)/2,dy,ax,(dy+ay)/2,ax,ay]
return path.join(' ')
})
function add_dot(g,data) {
g.selectAll('dots')
.data(data)
.join('circle')
.attr('class','dots')
.attr('cx',d => d[0])
.attr('cy',d => d[1])
.attr('r',5)
.attr('fill','red')
}
}
<script src="https://d3js.org/d3.v6.min.js"></script>

All fillTexted text crammed in one place in HTML5 canvas

I want to display an hexagonal grid where each cell displays its coordinate in the system (thus the top leftmost cell will have a1 written in it, the one immediately right to it will have b1 written in it, etc).
The code below is meant to achieve this (and nearly does it).
However all the texts are crammed in one small place (even though I specified the location on line 48 of the code, and this location seems correct since the rest of the image is fine). What am I doing wrong ?
<html>
<head>
<meta charset="utf-8">
<title>Hex board</title>
<script type="text/javascript">
var r = 20;
var w = r*2*(Math.sqrt(3)/2);
var ctx;
var mainWidth = 850;
var mainHeight = 600;
var dim = 11;
var i,x,y, txt;
var alphabet =["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"];
function textFromCoordinates(x,y)
{
return(alphabet[x]+(y+1));
}
function drawHexagon(c, x, y, r)
{
c.beginPath();
c.moveTo(x, y-r);
for(i=0; i<6; i++) {
c.lineTo(x+r*Math.cos(Math.PI*(1.5+1/3*i)), y+r*Math.sin(Math.PI*(1.5+1/3*i)));
}
c.closePath();
c.fill();
c.stroke();
}
function draw()
{
ctx.clearRect(0, 0, mainWidth, mainHeight);
ctx.lineWidth = 1;
ctx.strokeStyle = "white";
for(y=0; y<dim; y++)
{
for(x=0; x<dim; x++)
{
ctx.fillStyle = "rgb(" + (x+241) + "," + (y+220) + ",178)";
drawHexagon(ctx, (x+y)*w - (y-4)*(w/2), (y+2)*1.5*r, r);
txt = textFromCoordinates(x,y);
ctx.font = 'italic 40pt Calibri';
ctx.fillStyle = "black";
ctx.moveTo((x+y)*w - (y-4)*(w/2), (y+2)*1.5*r);
ctx.fillText(txt,mainWidth/dim,mainHeight/dim);
}
}
}
function load()
{
var canvas = document.getElementById("output");
ctx = canvas.getContext("2d");
draw();
}
</script>
</head>
<body onload="load()">
<canvas style="position:absolute,top:0px,left:20px" width="850" height="600" id="output">Canvas not supported...</canvas>
</body>
</html>
You need to draw the text at the same coordinates as the hexagon:
ctx.fillText(txt,(x+y)*w - (y-4)*(w/2),(y+2)*1.5*r);
Also I've changed the font to a smaller size, and I'm aligning it around the center:
ctx.font = 'italic 16px Calibri';
ctx.textAlign = "center";
ctx.textBaseline = "middle";
I hope this is what you need.
var r = 20;
var w = r*2*(Math.sqrt(3)/2);
var ctx;
var mainWidth = 850;
var mainHeight = 600;
var dim = 11;
var i,x,y, txt;
var alphabet =["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"];
function textFromCoordinates(x,y)
{
return(alphabet[x]+(y+1));
}
function drawHexagon(c, x, y, r)
{
c.beginPath();
c.moveTo(x, y-r);
for(i=0; i<6; i++) {
c.lineTo(x+r*Math.cos(Math.PI*(1.5+1/3*i)), y+r*Math.sin(Math.PI*(1.5+1/3*i)));
}
c.closePath();
c.fill();
c.stroke();
}
function draw()
{
ctx.clearRect(0, 0, mainWidth, mainHeight);
ctx.lineWidth = 1;
ctx.strokeStyle = "white";
for(y=0; y<dim; y++)
{
for(x=0; x<dim; x++)
{
ctx.fillStyle = "rgb(" + (x+241) + "," + (y+220) + ",178)";
drawHexagon(ctx, (x+y)*w - (y-4)*(w/2), (y+2)*1.5*r, r);
txt = textFromCoordinates(x,y);
ctx.font = 'italic 16px Calibri';
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = "black";
ctx.fillText(txt,(x+y)*w - (y-4)*(w/2),(y+2)*1.5*r);
}
}
}
function load()
{
var canvas = document.getElementById("output");
ctx = canvas.getContext("2d");
draw();
}
load()
canvas{border:1px solid}
<canvas style="position:absolute,top:0px,left:20px" width="850" height="600" id="output">Canvas not supported...</canvas>

PGraphics set different colormode not working

I have the following code:
import processing.video.*;
import oscP5.*;
import netP5.*;
//sending the data to wekinator
int numPixelsOrig, numPixels, boxWidth = 64, boxHeight = 48, numHoriz = 640/boxWidth, numVert = 480/boxHeight;
color[] downPix = new color[numHoriz * numVert];
PGraphics buffer;
Capture video;
OscP5 oscSend;
//recieving data from the wekinatorino
ArrayList<Blob> blobs = new ArrayList<Blob>();
int amt = 5;
int cons1 = 200, cons2 = 150;
float xspeed, yspeed, radius;
float p1, p2, p3, p4, p5, p6;
OscP5 oscRecieve;
NetAddress dest;
void setup() {
colorMode(RGB, 100);
size(600, 600, P2D);
buffer = createGraphics(600, 600, P3D);
buffer.beginDraw();
buffer.colorMode(HSB, 100);
buffer.endDraw();
String[] cameras = Capture.list();
if (cameras == null) {
video = new Capture(this, 640, 480);
}
if (cameras.length == 0) {
exit();
} else {
video = new Capture(this, 640, 480);
video.start();
numPixelsOrig = video.width * video.height;
}
oscSend = new OscP5(this, 9000);
oscRecieve = new OscP5(this, 12000);
dest = new NetAddress("127.0.0.1", 6448);
}
void draw() {
//println(blobs.size());
if (video.available() == true) {
video.read();
video.loadPixels();
int boxNum = 0;
int tot = boxWidth*boxHeight;
for (int x = 0; x < 640; x += boxWidth) {
for (int y = 0; y < 480; y += boxHeight) {
float red = 0, green = 0, blue = 0;
for (int i = 0; i < boxWidth; i++) {
for (int j = 0; j < boxHeight; j++) {
int index = (x + i) + (y + j) * 640;
red += red(video.pixels[index]);
green += green(video.pixels[index]);
blue += blue(video.pixels[index]);
}
}
downPix[boxNum] = color(red/tot, green/tot, blue/tot);
fill(downPix[boxNum]);
int index = x + 640*y;
red += red(video.pixels[index]);
green += green(video.pixels[index]);
blue += blue(video.pixels[index]);
noStroke();
rect(x, y, boxWidth, boxHeight);
boxNum++;
}
}
if (frameCount % 2 == 0)
sendOsc(downPix);
}
if (blobs.size() < amt) {
blobs.add(new Blob(new PVector(random(100 + (width - 200)), random(100 + (height- 200))), new PVector(-3, 3), random(100, 300)));
}
for (int i = blobs.size() - 1; i >= 0; i--) {
if (blobs.size() > amt) {
blobs.remove(i);
}
}
buffer.beginDraw();
buffer.loadPixels();
for (int x = 0; x < buffer.width; x++) {
for (int y = 0; y < buffer.height; y++) {
int index = x + y * buffer.width;
float sum = 0;
for (Blob b : blobs) {
float d = dist(x, y, b.pos.x, b.pos.y);
sum += 10 * b.r / d;
}
buffer.pixels[index] = color(sum, 255, 255); //constrain(sum, cons1, cons2)
}
}
buffer.updatePixels();
buffer.endDraw();
for (Blob b : blobs) {
b.update();
}
//if () {
//xspeed = map(p1, 0, 1, -5, 5);
//yspeed = map(p2, 0, 1, -5, 5);
//radius = map(p3, 0, 1, 100, 300);
//cons1 = int(map(p4, 0, 1, 0, 255));
//cons2 = int(map(p5, 0, 1, 0, 255));
//amt = int(map(p6, 0, 1, 1, 6));
//for (Blob b : blobs) {
// b.updateAlgorithm(xspeed, yspeed, radius);
//}
//}
image(buffer, 0, 0);
}
void sendOsc(int[] px) {
//println(px);
OscMessage msg = new OscMessage("/wek/inputs");
for (int i = 0; i < px.length; i++) {
msg.add(float(px[i]));
}
oscSend.send(msg, dest);
}
void oscEvent(OscMessage theOscMessage) {
if (theOscMessage.checkAddrPattern("/wek/outputs")==true) {
if (theOscMessage.checkTypetag("fff")) {
p1 = theOscMessage.get(0).floatValue();
p2 = theOscMessage.get(1).floatValue();
p3 = theOscMessage.get(2).floatValue();
p4 = theOscMessage.get(2).floatValue();
p5 = theOscMessage.get(2).floatValue();
p6 = theOscMessage.get(2).floatValue();
} else {
}
}
}
void mousePressed() {
xspeed = random(-5, 5);
yspeed = random(-5, 5);
radius = random(100, 300);
cons1 = int(random(255));
cons2 = int(random(255));
amt = int(random(6));
for (Blob b : blobs) {
b.updateAlgorithm(xspeed, yspeed, radius);
}
}
class Blob {
PVector pos;
PVector vel;
float r;
Blob(PVector pos, PVector vel, float r) {
this.pos = pos.copy();
this.vel = vel.copy();
this.r = r;
}
void update(){
pos.add(vel);
if (pos.x > width || pos.x < 0) {
vel.x *= -1;
}
if (pos.y > height || pos.y < 0) {
vel.y *= -1;
}
}
void updateAlgorithm(float vx, float vy, float nr){
vel.x = vx;
vel.y = vy;
r = nr;
}
}
Then I create some graphics in the buffer element. but the graphics aren't using my HSB color mode with the result i only see blue and white...
so how do i correct my code, or change the colorMode for a PGraphics element to HSB?
According to the PGraphics reference:
The beginDraw() and endDraw() methods (see above example) are
necessary to set up the buffer and to finalize it
therefore you should try this:
buffer = createGraphics(600, 600, P3D);
buffer.beginDraw();
buffer.colorMode(HSB, 100);
buffer.endDraw();
Here's a full test sketch to run and compare:
PGraphics buffer;
void setup(){
colorMode(RGB, 100);
size(600, 600, P2D);
//draw test gradient in RGB buffer
noStroke();
for(int i = 0 ; i < 10; i++){
fill(i * 10,100,100);
rect(0,i * 60,width,60);
}
buffer = createGraphics(600, 600, P3D);
buffer.beginDraw();
buffer.colorMode(HSB, 100);
buffer.endDraw();
//draw test gradient in HSB buffer
buffer.beginDraw();
buffer.noStroke();
for(int i = 0 ; i < 10; i++){
buffer.fill(i * 10,100,100);
buffer.rect(0,i * 60,width,60);
}
buffer.endDraw();
//finally render the buffer on screen, offset to the right for comparison
image(buffer,300,0);
}

radial gradient color effect on concentric circles

How to apply gradient colors on 5 concentric circles using html5 canvas
gradient = context.createRadialGradient(startx, starty,radAvg, xEnd, yEnd,radAvg);
gradient.addColorStop(0, startColor);
gradient.addColorStop(1.0, endColor);
Current results:
JavaScript code:
var colors = ["#B8D430", "#3AB745", "#029990", "#3501CB",
"#2E2C75", "#673A7E", "#CC0071", "#F80120",
"#F35B20", "#FB9A00", "#FFCC00", "#FEF200"];
var restaraunts = ["Wendy's", "McDonalds", "Chick-fil-a", "Five Guys",
"Gold Star", "La Mexicana", "Chipotle", "Tazza Mia",
"Panera", "Just Crepes", "Arby's", "Indian"];
var startAngle = 0;
var arc = Math.PI / 6;
var spinTimeout = null;
var spinArcStart = 10;
var spinTime = 0;
var spinTimeTotal = 0;
var ctx;
function draw() {
drawRouletteWheel();
}
function drawRouletteWheel() {
var canvas = document.getElementById("wheelcanvas");
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 sans-serif';
for(var i = 0; i < 12; 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 = restaraunts[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);
drawRouletteWheel();
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 sans-serif';
var text = restaraunts[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);
}
draw();
here is my solution
var Mid_diff=(startAngle+endAngle)/2;
var dest_x=arc.center.x+(arc.maxRadius*Math.cos(Mid_diff));
var dest_y=arc.center.y+(arc.maxRadius*Math.sin(Mid_diff));
var gradient = context.createRadialGradient( arc.center.x,arc.center.y,0,dest_x,dest_y,arc.maxRadius);
gradient.addColoStop(0,rgba(0,12,107,0);
gradient.addColoStop(1,rgba(0,12,107,1);
context.strokeStyle = gradient;
worked

Set object drag limit in Fabric.js

i am new in fabric js want to set the drag limit
i have also try with https://github.com/kangax/fabric.js/wiki/Working-with-events
not able to get the solution.
please check the attached image, object can move anyware but it should be display in red area only.i want this. help me...thanks in advance !!
While Orangepill's answer is correct, it produces a "stuttering" when your object hits the object bounds. If you have a rectangular bounding box (and not a complex bounding object) an alternative is to allow the object to be dragged along the bounds and "slide" along the bounding box. You do this by capping the coordinates values and letting the other dimension move as usual. An example snippet would look like so:
var canvas = new fabric.Canvas("bounded");
var boundingBox = new fabric.Rect({
fill: "none",
width: 600,
height: 400,
hasBorders: false,
hasControls: false,
lockMovementX: true,
lockMovementY: true,
evented: false,
stroke: "red"
});
var movingBox = new fabric.Rect({
width: 100,
height: 100,
hasBorders: false,
hasControls: false
});
canvas.on("object:moving", function() {
var top = movingBox.top;
var bottom = top + movingBox.height;
var left = movingBox.left;
var right = left + movingBox.width;
var topBound = boundingBox.top;
var bottomBound = topBound + boundingBox.height;
var leftBound = boundingBox.left;
var rightBound = leftBound + boundingBox.width;
// capping logic here
movingBox.setLeft(Math.min(Math.max(left, leftBound), rightBound - movingBox.width));
movingBox.setTop(Math.min(Math.max(top, topBound), bottomBound - movingBox.height));
});
canvas.add(boundingBox);
canvas.add(movingBox);
See this example in JSFiddle here
Felix Fung's answer was a starting point, but there are many things to consider. Here is a version that accounts for some of them.
It handles the canvas having a viewport transform (ie, zoomed/panned) and objects that are center-origined instead of left/top-origined. It also constrains objects wider/taller than the viewport to the top/left instead of the bottom/right.
canvas.on("object:moving", function(e) {
var obj = e.target;
var canvas = obj.canvas;
var top = obj.top;
var left = obj.left;
var zoom = canvas.getZoom();
var pan_x = canvas.viewportTransform[4];
var pan_y = canvas.viewportTransform[5];
// width & height we are constraining to must be calculated by applying the inverse of the current viewportTransform
var c_width = canvas.width / zoom;
var c_height = canvas.height / zoom;
var w = obj.width * obj.scaleX
var left_adjust, right_adjust
if(obj.originX == "center") {
left_adjust = right_adjust = w / 2;
} else {
left_adjust = 0;
right_adjust = w;
}
var h = obj.height * obj.scaleY;
var top_adjust, bottom_adjust;
if(obj.originY == "center") {
top_adjust = bottom_adjust = h / 2;
} else {
top_adjust = 0;
bottom_adjust = h;
}
// if you need margins set them here
var top_margin = 0;
var bottom_margin = 0;
var left_margin = 0;
var right_margin = 0;
var top_bound = top_margin + top_adjust - pan_y;
var bottom_bound = c_height - bottom_adjust - bottom_margin - pan_y;
var left_bound = left_margin + left_adjust - pan_x;
var right_bound = c_width - right_adjust - right_margin - pan_x;
if( w > c_width ) {
obj.setLeft(left_bound);
} else {
obj.setLeft(Math.min(Math.max(left, left_bound), right_bound));
}
if( h > c_height ) {
obj.setTop(top_bound);
} else {
obj.setTop(Math.min(Math.max(top, top_bound), bottom_bound));
}
});
What had worked for me is to create an event listener for the object:moving event. When the move is happening you update the goodtop and goodleft variables and once you are out of bounds to reposition the object to the last good points.
var goodtop, goodleft, boundingObject;
canvas.on("object:moving", function(){
var obj = this.relatedTarget;
var bounds = boundingObject;
obj.setCoords();
if(!obj.isContainedWithinObject(bounds)){
obj.setTop(goodtop);
obj.setLeft(goodleft);
canvas.refresh();
} else {
goodtop = obj.top;
goodleft = obj.left;
}
});
I used Michael Johnston's snipped as a starting point to add bounding control for rotated elements. This snipped only covers the cases when either (obj.centerX && obj.centerY == "center") || (obj.centerX && obj.centerY != "center")
canvasRef.current.on("object:moving", function (e) {
var obj = e.target;
var canvas = obj.canvas;
var zoom = canvas.getZoom();
var pan_x = canvas.viewportTransform[4];
var pan_y = canvas.viewportTransform[5];
// get left, top, width, and height of object
var left = obj.left;
var top = obj.top;
var width = obj.width * obj.scaleX;
var height = obj.height * obj.scaleY;
// width & height we are constraining to must be calculated by applying the inverse of the current viewportTransform
var c_width = canvas.width / zoom;
var c_height = canvas.height / zoom;
// calculate values that define the origin of the object, when it is centered in the center or not
var left_adjust, right_adjust;
if (obj.originX == "center") {
left_adjust = right_adjust = width / 2;
} else {
left_adjust = 0;
right_adjust = width;
}
var top_adjust, bottom_adjust;
if (obj.originY == "center") {
top_adjust = bottom_adjust = height / 2;
} else {
top_adjust = 0;
bottom_adjust = height;
}
// support for rotated objects
if (obj.angle) {
var angle = obj.angle;
if (angle > 270) {
angle -= 270;
} else if (angle > 180) {
angle -= 180;
} else if (angle > 90) {
angle -= 90;
}
const radians = angle * (Math.PI / 180);
const w_opposite = width * Math.sin(radians);
const w_adjacent = width * Math.cos(radians);
const h_opposite = height * Math.sin(radians);
const h_adjacent = height * Math.cos(radians);
if (obj.originX != "center" && obj.originY != "center") {
if (obj.angle <= 90) {
left_adjust = h_opposite;
top_adjust = 0;
right_adjust = w_adjacent;
bottom_adjust = h_adjacent + w_opposite;
} else if (obj.angle > 90 && obj.angle <= 180) {
left_adjust = h_adjacent + w_opposite;
top_adjust = h_opposite;
right_adjust = 0;
bottom_adjust = w_adjacent;
} else if (obj.angle > 180 && obj.angle <= 270) {
left_adjust = w_adjacent;
top_adjust = w_opposite + h_adjacent;
right_adjust = h_opposite;
bottom_adjust = 0;
} else {
left_adjust = 0;
top_adjust = w_adjacent;
right_adjust = w_opposite + h_adjacent;
bottom_adjust = h_opposite;
}
}
if (obj.originX == "center" && obj.originY == "center") {
if (obj.angle <= 90 || (obj.angle > 180 && obj.angle <= 270)) {
left_adjust = (w_adjacent + h_opposite) / 2;
right_adjust = (w_adjacent + h_opposite) / 2;
top_adjust = (h_adjacent + w_opposite) / 2;
bottom_adjust = (h_adjacent + w_opposite) / 2;
} else {
left_adjust = (h_adjacent + w_opposite) / 2;
right_adjust = (h_adjacent + w_opposite) / 2;
top_adjust = (w_adjacent + h_opposite) / 2;
bottom_adjust = (w_adjacent + h_opposite) / 2;
}
}
}
// if you need margins set them here
var top_margin = 0;
var bottom_margin = 0;
var left_margin = 0;
var right_margin = 0;
var top_bound = top_margin + top_adjust - pan_y;
var bottom_bound = c_height - bottom_adjust - bottom_margin - pan_y;
var left_bound = left_margin + left_adjust - pan_x;
var right_bound = c_width - right_adjust - right_margin - pan_x;
if (width > c_width) {
obj.set("left", left_bound);
} else {
obj.set("left", Math.min(Math.max(left, left_bound), right_bound));
}
if (height > c_height) {
obj.set("top", top_bound);
} else {
obj.set("top", Math.min(Math.max(top, top_bound), bottom_bound));
}
});
SEE THE WORKING EXAMPLE
It's as simple as a water try this
just use this js
<script type="text/javascript">
//use global variable for canvas object
var canvas;
var ctx;
function onLoad() {
//get fabric canvas with id mycanvas
canvas = new fabric.Canvas('mycanvas');
canvas.on("mouse : down",function{
//get canvas 2d context
ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.rect(115,60,221,390);//specify bounded rectangle
ctx.closePath();
ctx.clip();
ctx.save();
});
//now restore the context on mouse up
canvas.on("mouse : up",function(){
ctx.restore();//restore the context
});
}
</script>
Hope this will help you.
Njoy coding :)
now u can have the same clipping region for every object supported by Fabric.js for transformation and moving.
try it :).

Resources