I am using Konva to create an area on a webpage. I create a dragBoundFunc function for my rectangle checking pos.x and pos.y and width of canvas and rect and this works fine - the rectangle only stays in this area as I want it to when dragged.
My problem comes when I rotate the rectangle 90 degrees. Now width is less than height when I check getClientRect().width and getClientRect().height which is expected but it now won't drag all the way to the end of my canvas unless I detect end of rotation and change my shapes offsetY value to 65 and then it works fine.
Is changing offsetY and offsetX after rotation the right approach to fix this problem and how do I calculate what to set them to if yes?
dragBoundFunc: function(pos) {
var iw = 600 - (my_shape.getClientRect().width);
var ih = 400 - my_shape.getClientRect().height;
var newX = pos.x > iw ? iw : pos.x;
var newY = pos.y > ih ? ih : pos.y;
return {
x: newX > 0 ? newX : 0,
y: newY > 0 ? newY : 0
};
}
Here is a working solution. Note that this works only for fixed rotations of 0, 90, 180 and 270 degrees or multiples. A solution for 'any-angle-of-rotation; would require trig or matrix maths to compute rotated values of the point, etc.
/*
This is the drag bounds func
*/
function theDragFunc(pos) {
var thisRect = {x: this.x(), y: this.y(), width: this.width(), height: this.height()};
// copy the boundary rect into a testRect which defines the extent of the dragbounds
// without accounting for the width and height of dragging rectangle.
// This is changed below depending on rotation.
var testRect={
left: boundary.x,
top: boundary.y,
right: boundary.x + boundary.width,
bottom: boundary.y + boundary.height
};
// the userRotation value is calculated in the rotation button onclick
// to be one of 0, 90, 180, 270
switch (userRotation){
case 0: // for 0 degrees compute as per a normal bounds rect
testRect.right = testRect.right - thisRect.width;
testRect.bottom = testRect.bottom - thisRect.height;
break;
case 90: // for 90 degs we have to modify the test boundary left and bottom
testRect.left = testRect.left + thisRect.height;
testRect.bottom = testRect.bottom - thisRect.width;
break;
case 180: // for 180 degs we have to modify the test boundary left and top
testRect.left = testRect.left + thisRect.width;
testRect.top = testRect.top + thisRect.height;
break;
case 270: // for 270 degs we have to modify the test boundary right and top
testRect.right = testRect.right - thisRect.height;
testRect.top = testRect.top + thisRect.width;
break;
}
// get new pos as: if pos inside bounday ranges then use it, otherwise user boundary
// left edge check
var newX = (pos.x < testRect.left ? testRect.left : pos.x);
// right edge check
newX = (newX > testRect.right ? testRect.right : newX);
// top edge check
var newY = (pos.y < testRect.top ? testRect.top : pos.y);
// bottom edge check
newY = (newY > testRect.bottom ? testRect.bottom : newY);
// return the point we calculated
return {
x: newX,
y: newY
}
}
// From here on is just the canvas setup etc.
// set ub the main rect - the one we drag and rotate
var target = {x: 70, y: 70, width: 70, height: 40};
// set ub the boundary rect - used in the rectfunc later
var boundary = {x: 20, y: 20, width: 460, height: 160};
// Set up the stage
var s1 = new Konva.Stage({container: 'container1', width: 500, height: 200});
// add a layer.
var layer1 = new Konva.Layer({draggable: false});
s1.add(layer1);
// show the extent of the boundary
var funcRect = new Konva.Rect({
x:boundary.x,
y: boundary.y,
width: boundary.width,
height: boundary.height,
stroke: 'red'})
layer1.add(funcRect)
// Make some easy-to-grok values for the boundary func.
boundary.minX = boundary.x;
boundary.maxX = boundary.x + boundary.width;
boundary.minY = boundary.y;
boundary.maxY = boundary.y + boundary.height;
// show the target rect
var targetRect = new Konva.Rect({
x:target.x,
y: target.y,
width: target.width,
height: target.height,
stroke: 'green',
draggable: true,
// Apply a linear graient fill to give a sense of rotation.
fillLinearGradientStartPoint: { x : -50, y : -50},
fillLinearGradientEndPoint: { x : 50, y : 50},
fillLinearGradientColorStops: [0, 'red', 1, 'yellow'],
dragBoundFunc: theDragFunc // the function is at the bottom top of the code
})
layer1.add(targetRect)
// Draw the stage
s1.draw();
var userRotation = 0;
$('#btnRotate').on('click', function(e){
targetRect.rotate(90)
s1.draw();
var rectRotation = targetRect.rotation();
// user can rotate > 360 so we will nomalise the rotation down to range 0 - 270
userRotation = (rectRotation / 90);
userRotation = (userRotation % 4) * 90;
$('#info').html("Rect rotation " + rectRotation + " same as " + userRotation);
})
p
{
padding: 4px;
}
#container1
{
display: inline-block;
width: 500px;
height: 200px;
background-color: silver;
overflow: hidden;
}
#pallette
{
height: 52px; width: 500px;
border: 1px solid #666;
margin-bottom: 10px;
z-index: 10;
}
.draggable
{
width:50px;
height: 50px;
display: inline-block;
border: 1px solid #666;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/konvajs/konva/1.6.5/konva.min.js"></script>
<p>Drag bounds function on rotated rect. Red rectangle is the extent of the bounds function. Drag the rect to the boundary and notice it is captured. Now click to rotate by 90%. A simple rect-based dragFunc would fail because the origin of the rect is not top-right. The solution function solves this.
</p>
<p>
<button id='btnRotate'>Rotate by +90 degrees</button> <span id='info'>0</span> degrees.
</p>
<div id='container1'></div>
Related
Its basicly an image, but I want to add some points with dropdowns, and its like 15 points, ajusting it with px would be very time consuming, I wonder if there is any other way around it. Thanks
<div id="map">
<div id="slide">
<img id="mapimg" src="https://i.imgur.com/NLS1KX0.jpg">
<div id="point">
<i id="pointIcon" class="fa-solid fa-location-dot"></i>
</div>
</div>
</div>
$(document).ready(function (){
var scroll_zoom = new ScrollZoom($('#map'),5,0.5)
})
//The parameters are:
//
//container: The wrapper of the element to be zoomed. The script will look for the first child of the container and apply the transforms to it.
//max_scale: The maximum scale (4 = 400% zoom)
//factor: The zoom-speed (1 = +100% zoom per mouse wheel tick)
function ScrollZoom(container,max_scale,factor){
var target = container.children().first()
var size = {w:target.width(),h:target.height()}
var pos = {x:0,y:0}
var scale = 1
var zoom_target = {x:0,y:0}
var zoom_point = {x:0,y:0}
var curr_tranform = target.css('transition')
var last_mouse_position = { x:0, y:0 }
var drag_started = 0
target.css('transform-origin','0 0')
target.on("mousewheel DOMMouseScroll",scrolled)
target.on('mousemove', moved)
target.on('mousedown', function() {
drag_started = 1;
target.css({'cursor':'move', 'transition': 'transform 0s'});
/* Save mouse position */
last_mouse_position = { x: event.pageX, y: event.pageY};
});
target.on('mouseup mouseout', function() {
drag_started = 0;
target.css({'cursor':'default', 'transition': curr_tranform});
});
function scrolled(e){
var offset = container.offset()
zoom_point.x = e.pageX - offset.left
zoom_point.y = e.pageY - offset.top
e.preventDefault();
var delta = e.delta || e.originalEvent.wheelDelta;
if (delta === undefined) {
//we are on firefox
delta = e.originalEvent.detail;
}
delta = Math.max(-1,Math.min(1,delta)) // cap the delta to [-1,1] for cross browser consistency
// determine the point on where the slide is zoomed in
zoom_target.x = (zoom_point.x - pos.x)/scale
zoom_target.y = (zoom_point.y - pos.y)/scale
// apply zoom
scale += delta * factor * scale
scale = Math.max(1,Math.min(max_scale,scale))
// calculate x and y based on zoom
pos.x = -zoom_target.x * scale + zoom_point.x
pos.y = -zoom_target.y * scale + zoom_point.y
update()
}
function moved(event){
if(drag_started == 1) {
var current_mouse_position = { x: event.pageX, y: event.pageY};
var change_x = current_mouse_position.x - last_mouse_position.x;
var change_y = current_mouse_position.y - last_mouse_position.y;
/* Save mouse position */
last_mouse_position = current_mouse_position;
//Add the position change
pos.x += change_x;
pos.y += change_y;
update()
}
}
function update(){
// Make sure the slide stays in its container area when zooming out
if(pos.x>0)
pos.x = 0
if(pos.x+size.w*scale<size.w)
pos.x = -size.w*(scale-1)
if(pos.y>0)
pos.y = 0
if(pos.y+size.h*scale<size.h)
pos.y = -size.h*(scale-1)
target.css('transform','translate('+(pos.x)+'px,'+(pos.y)+'px) scale('+scale+','+scale+')')
}
}
I tried using this, but the image is very long, and it would take to much time, I wonder if there is any easy way to do it.
#point {
position: relative;
overflow: hidden;
margin-left: 338px;
margin-top: -243px;
width: 25px;
height: 25px;
}
I tried using this, but the image is very long, and it would take to much time, I wonder if there is any easy way to do it.
#point {
position: relative;
overflow: hidden;
margin-left: 338px;
margin-top: -243px;
width: 25px;
height: 25px;
}
I am using the library FabricJS to overlay text on canvas. I need to add padding (ideally just left & right) to a text element that includes the property textBackgroundColor.
Here is what I've tried so far:
let textObject = new fabric.Text('Black & White',{
fontFamily: this.theme.font,
fontSize: this.theme.size,
textBaseline: 'bottom',
textBackgroundColor: '#000000',
left: 0,
top: 0,
width: 100,
height: 40,
padding: 20,
fill: 'white',
});
The padding doesn't work as I anticipated. I have attempted to use the backgroundColor property but that adds background to the whole group block and not just the text.
I could add a a non-breaking space to achieve the same effect, but this doesn't seem like a reliable solution and I was hoping Fabric JS allowed this out-of-the-box. Any ideas how to achieve this?
Required solution (version on the right, with additional padding is what I would like):
I give you 2 answers for two subtly different cases.
Case 1 - padding around the bounding box of single of multi-line text.
The code follows the CSS approach where margin is outside of the box, as depicted by the red line, and padding is inside, as shown by the gold background. On the left hand image, black text background is what you get from the built-in 'textBackgroundColor'. The yellow area shows the padding currently applied. The right hand image shows the additional benefit when you harmonise the padding colour, an also that you can reduce opacity on the background whilst keeping the text full-opaque.
BTW the built-in 'padding' attribute for text pads in relation to the controlling border, but the background color fill does not cover the white-space created. In other words, it operates like CSS margin rather than CSS padding.
Therefore it is necessary to ignore this padding attribute, and instead introduce a coloured rect to give the background color required, grouping this with the text element and positioning accordingly.
Example snippet below.
var canvas = window._canvas = new fabric.Canvas('c');
// function to do the drawing. Could easily be accomodated into a class (excluding the canvas reset!)
function reset(pos)
{
canvas.clear();
// Create the text node - note the position is (0, 0)
var text = new fabric.Text(pos.text, {
fontFamily: 'Arial',
left: 0,
top: 0,
fill: "#ffffff",
stroke: "",
textBackgroundColor: '#000000'
});
// create the outer 'margin' rect, note the position is negatively offset for padding & margin
// and the width is sized from the dimensions of the text node plus 2 x (padding + margin).
var rectMargin = new fabric.Rect({
left: -1 * (pos.padding.left + pos.margin.left),
top: -1 * (pos.padding.top + pos.margin.top),
width: text.width + ((pos.padding.left + pos.padding.right) + (pos.margin.left + pos.margin.right)),
height: text.height + ((pos.padding.top + pos.padding.bottom) + (pos.margin.top + pos.margin.bottom)),
strokeWidth: pos.border,
stroke: 'red',
fill: 'transparent'
})
// create the inner 'padding' rect, note the position is offset for padding only
// and the width is sized from the dimensions of the text node plus 2 x padding.
var rectPadding = new fabric.Rect({
width: text.width + (pos.padding.left + pos.padding.right),
height: text.height + (pos.padding.top + pos.padding.bottom),
left: -1 * pos.padding.left, top: -1 * pos.padding.top,
fill: 'gold'
})
// create group and add shapes to group, rect first so it is below text.
// note that as the group is oversized, we position it at pos - padding.
var group = new fabric.Group([ rectMargin, rectPadding, text ], {
left: pos.x - (pos.padding.left - pos.margin.left),
top: pos.y - (pos.padding.top - pos.margin.top),
angle: pos.angle,
});
canvas.add(group);
}
// function to grab values from user inputs
function go()
{
var m = $('#margin').val().split(',');
var p = $('#padding').val().split(',');
for (var i = 0 ; i < 4; i = i + 1)
{
p[i] = parseInt(p[i], 10); // ensure we have numbers and not strings !
m[i] = parseInt(m[i], 10);
}
// Object holding position and content info
var pos = {x: 50, y : 10, text: 'Text with padding\nand another line',
padding: {top:p[0], right:p[1], bottom: p[2], left: p[3]}, margin: {top:m[0], right:m[1], bottom: m[2], left: m[3]}, border: 1, angle: 10};
reset(pos);
}
// click handler for go button
$('#go').on('click', function(e){
go();
})
// call go once to show on load
go();
div
{
background-color: silver;
width: 600px;
height: 300px;
}
.ipt
{
margin-right: 20px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.1/fabric.min.js"></script>
<p>
<span class='ipt'> Margin: <input id='margin' value = '12,10,12,10' /></span>
<span class='ipt'> Padding: <input id='padding' value = '0,5,0,5' /></span>
<span class='ipt'><button id='go' />Go</button></span>
<div>
<canvas id="c" width="600" height="300"></canvas>
</div>
Case 2: padding boxing the individual text lines and not the full bounding box.
In this case you can see the difference in how the padded background tracks each line of text instead of applying to the outer bounding box of the text. The solution is more complex, involving creating a dummy text node which then provides line splitting and sizing information. We then loop thru the line data, outputting individual text lines and padding rects into a group which means we can position and handle the text as a single object, as illustrated by the applied angle.
var textIn = 'Text goat\nMillenium jam\nplumb\nBlack & White'
var canvas = window._canvas = new fabric.Canvas('c');
// function to do the drawing. Could easily be accomodated into a class (excluding the canvas reset!)
function reset(pos)
{
canvas.clear();
// Create the text measuring node - not added to the canvas !
var textMeasure = new fabric.IText(pos.text, {
fontFamily: 'Arial',
left: 0,
top: 0,
fill: "#ffffff",
stroke: "",
textBackgroundColor: '#000000'
});
// loop round the lines in the text creating a margin/pad scenario for each line
var theText, text, textHeight, rectPadding, rectMargin, top = 0, shapes = [];
for (var i = 0; i < textMeasure._textLines.length; i = i + 1){
theText = textMeasure._textLines[i].join('');
textHeight = Math.floor(textMeasure.lineHeight * textMeasure.fontSize) //textMeasure.getHeightOfLine(i)
// Make the text node for line i
text = new fabric.IText(theText, {
fontFamily: 'Arial',
left: 0,
top: top,
fill: "#ffffff",
stroke: ""
});
// create the outer 'margin' rect, note the position is negatively offset for padding & margin
// and the width is sized from the dimensions of the text node plus 2 x (padding + margin).
rectMargin = new fabric.Rect({
left: -1 * (pos.padding.left + pos.margin.left),
top: top - (pos.padding.top + pos.margin.top),
width: text.width + ((pos.padding.left + pos.padding.right) + (pos.margin.left + pos.margin.right)),
height: textHeight + ((pos.padding.top + pos.padding.bottom) + (pos.margin.top + pos.margin.bottom)),
fill: 'transparent'
})
shapes.push(rectMargin);
// create the inner 'padding' rect, note the position is offset for padding only
// and the width is sized from the dimensions of the text node plus 2 x padding.
rectPadding = new fabric.Rect({
width: text.width + (pos.padding.left + pos.padding.right),
height: textHeight + (pos.padding.top + pos.padding.bottom),
left: -1 * pos.padding.left,
top: top - pos.padding.top,
fill: '#000000ff'
})
shapes.push(rectPadding);
shapes.push(text);
// move the insert point down by the height of the line
var gap = 0; // text.lineHeight - textHeight;
top = top - 1 + textHeight + pos.padding.top + pos.margin.top + pos.padding.bottom + pos.margin.bottom;
}
// At this point we have a list of shapes to output in the shapes[] array.
// Create group and add the shapes to group.
// note that group is positioned so that the topleft of the first text line is where
// it would fall if it were a standard text node.
var group = new fabric.Group(shapes, {
left: pos.x - (pos.padding.left - pos.margin.left),
top: pos.y - (pos.padding.top - pos.margin.top),
angle: pos.angle,
});
canvas.add(group);
}
// function to grab values from user inputs
function go()
{
var m = $('#margin').val().split(',');
var p = $('#padding').val().split(',');
for (var i = 0 ; i < 4; i = i + 1)
{
p[i] = parseInt(p[i], 10); // ensure we have numbers and not strings !
m[i] = parseInt(m[i], 10);
}
// Object holding position and content info
var pos = {x: 70, y : 10, text: textIn,
padding: {top:p[0], right:p[1], bottom: p[2], left: p[3]}, margin: {top:m[0], right:m[1], bottom: m[2], left: m[3]}, border: 1, angle: 10};
reset(pos);
}
// click handler for go button
$('#go').on('click', function(e){
go();
})
// call go once to show on load
go();
div
{
background-color: silver;
width: 600px;
height: 100px;
}
.ipt
{
margin-right: 20px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.1/fabric.min.js"></script>
<p>
<span class='ipt'> Margin: <input id='margin' value = '0,0,0,0' /></span>
<span class='ipt'> Padding: <input id='padding' value = '5,15,5,15' /></span>
<span class='ipt'><button id='go' />Go</button></span>
<div>
<canvas id="c" width="600" height="300"></canvas>
</div>
If anyone is looking for a solution with Textbox,
Here's another solution you can try: https://github.com/fabricjs/fabric.js/issues/3731
var TextboxWithPadding = fabric.util.createClass(fabric.Textbox, {
_renderBackground: function(ctx) {
if (!this.backgroundColor) {
return;
}
var dim = this._getNonTransformedDimensions();
ctx.fillStyle = this.backgroundColor;
ctx.fillRect(
-dim.x / 2 - this.padding,
-dim.y / 2 - this.padding,
dim.x + this.padding * 2,
dim.y + this.padding * 2
);
// if there is background color no other shadows
// should be casted
this._removeShadow(ctx);
}
});
Extend the base class and overwrite the _renderBackground function.
Very simple and works well for me!
I have a rather trivial example where I'm attempting to insert an image on a Fabric.js canvas with the image centered at the mouse coordinates. The center of the cursor should be the exact center of the image.
I calculate the center of the image by halving its width and height and apply them as offsets to the left and top coordinates of the canvas.Image I'm inserting. Simple enough:
// Coordinates from the mouse click event.
// `x` and `y` are aliases for `clientX`, `clientY`, respectively
const x = event.e.x,
y = event.e.y
image.set({
left: x - (image.width / 2),
top: y - (image.height / 2)
})
canvas.add(image);
When the image is added, it's not quite center. In fact, there's a difference of 8 pixels on both x and y axes that I cannot account for.
This is the result:
The center of the image should be under the cursor.
If I manually set the offset to 28, the image is properly centered. But since I cannot account for the 8 extra pixels, this hack is unacceptable.
Working sample:
const canvas = new fabric.Canvas('c', { width: 400, height: 150 });
// Add an image centered x & y at the exact point the user clicks.
canvas.on('mouse:up', (opt) => {
canvas.clear();
const x = opt.e.x,
y = opt.e.y;
fabric.Image.fromURL('https://i.imgur.com/htyNxF6.png', (image) => {
/* Calculate the offset based on the image dimensions:
This does not work as expected. A 40x40 image has a x/y offsets of 20.
If we set the offset to 28, the image is centered at the cursor. Why 28?
Check the "Apply ..." checkbox to see this in action.
*/
const offsetX = chkOffset.checked ? 28 : (image.width / 2),
offsetY = chkOffset.checked ? 28 : (image.height / 2);
const left = x - offsetX,
top = y - offsetY;
image.set({
left: left,
top: top,
stroke: 0,
padding: 0,
centeredScaling: true,
hasControls: false,
strokeWidth: 0,
hasBorders: 0
});
canvas.add(image);
writeDebug(`Mouse at: x=${x}, y=${y};
Image placed at: x=${left}, y=${top}
Difference of ${Math.abs(left-x)}, ${Math.abs(top-y)}`);
});
// Show coordinates on mouse move
canvas.on('mouse:move', (opt) => {
const x = opt.e.x,
y = opt.e.y;
writeDebug(`Mouse coordinates: x=${x}, y=${y}`);
});
});
function writeDebug(message) {
document.getElementById('debug').innerText = message;
}
body {
font-family: Consolas;
}
#c {
border: 1px solid #ececec;
box-shadow: 2px 2px 5px #c0c0c0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.1.0/fabric.min.js"></script>
<canvas id="c"></canvas>
<label for="chkOffset">
<input type="checkbox" id="chkOffset" />
Apply 28px offset
</label>
<div id="debug">Click in the canvas to add the image</div>
When I was adding sample code to my question, I stumbled upon the answer. I set the padding of every element to 10 using CSS (* { padding: 10px }) to make everything look better and upon doing so discovered that the offset gap had increased.
Getting mouse coordinates using MouseEvent.e.x and MouseEvent.x.y are the culprit. I compared their values with MouseEvent.e.offsetX and MouseEvent.e.offsetY and found that the x and y values did not account for padding and margins.
Using offsetX and offsetY seemed to do the trick.
It seems if you want to get relevant mouse coordinates in Fabric.js, using offsetX and offsetY is the best bet as it accounts for offsets caused by CSS.
const canvas = new fabric.Canvas('c', { width: 400, height: 150 });
// Add an image centered x & y at the exact point the user clicks.
canvas.on('mouse:up', (opt) => {
canvas.clear();
// Use .offset instead:
const x = opt.e.offsetX,
y = opt.e.offsetY;
fabric.Image.fromURL('https://i.imgur.com/htyNxF6.png', (image) => {
const offsetX = (image.width / 2),
offsetY = (image.height / 2);
const left = x - offsetX,
top = y - offsetY;
image.set({
left: left,
top: top,
stroke: 0,
padding: 0,
centeredScaling: true,
hasControls: false,
strokeWidth: 0,
hasBorders: 0
});
canvas.add(image);
writeDebug(`Mouse at: x=${x}, y=${y};
Image placed at: x=${left}, y=${top}
Difference of ${Math.abs(left-x)}, ${Math.abs(top-y)}`);
});
// Show coordinates on mouse move
canvas.on('mouse:move', (opt) => {
const x = opt.e.offsetX,
y = opt.e.offsetY;
writeDebug(`Mouse coordinates: x=${x}, y=${y}`);
});
});
function writeDebug(message) {
document.getElementById('debug').innerText = message;
}
body {
font-family: Consolas;
}
#c {
border: 1px solid #ececec;
box-shadow: 2px 2px 5px #c0c0c0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.1.0/fabric.min.js"></script>
<canvas id="c"></canvas>
<div id="debug">Click in the canvas to add the image</div>
I'm using fabricjs (1.7.20) and would like to create a sort of "bleed area" where some space around the canvas isn't usable by the user; a sort of "wall" if you will, to stop objects from being moved to the sides of the canvas walls. How might I accomplish this?
var canvas = new fabric.Canvas("c");
canvas.setHeight(350);
canvas.setWidth(350);
canvas.add(new fabric.IText("Some text", {
top: 25,
}));
var circle = new fabric.Circle({
radius: 20, fill: 'green', left: 100, top: 100
});
var triangle = new fabric.Triangle({
width: 20, height: 30, fill: 'blue', left: 150, top: 150
});
canvas.add(circle, triangle);
canvas {
border: 1px solid #dddddd;
margin-top: 10px;
border-radius: 3px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.20/fabric.min.js"></script>
<canvas id="c"></canvas>
This was suggested to me and it is the closest I've gotten. I'm looking to do this, but 10px, give or take, from the border of the canvas.
canvas.on('object:moving', function (e) {
var obj = e.target;
// if object is too big ignore
if(obj.currentHeight > obj.canvas.height || obj.currentWidth > obj.canvas.width){
return;
}
obj.setCoords();
// top-left corner
if(obj.getBoundingRect().top < 0 || obj.getBoundingRect().left < 0){
obj.top = Math.max(obj.top, obj.top-obj.getBoundingRect().top);
obj.left = Math.max(obj.left, obj.left-obj.getBoundingRect().left);
}
// bot-right corner
if(obj.getBoundingRect().top+obj.getBoundingRect().height > obj.canvas.height || obj.getBoundingRect().left+obj.getBoundingRect().width > obj.canvas.width){
obj.top = Math.min(obj.top, obj.canvas.height-obj.getBoundingRect().height+obj.top-obj.getBoundingRect().top);
obj.left = Math.min(obj.left, obj.canvas.width-obj.getBoundingRect().width+obj.left-obj.getBoundingRect().left);
}
});
You can add a 10 pixel bleed area by adding / subtracting the value 10 from the conditions that determine if an object is being moved out of the “bleed” area (I'd actually prefer to call it “padding”), and adding / subtracting from the calculation that repositions the object inside the padding boundaries.
Here's an updated example that works:
var padding = 10;
var canvas = new fabric.Canvas("c");
canvas.setHeight(350);
canvas.setWidth(350);
canvas.add(new fabric.IText("Some text", {
top: 25,
}));
var circle = new fabric.Circle({
radius: 20,
fill: 'green',
left: 100,
top: 100
});
var triangle = new fabric.Triangle({
width: 20,
height: 30,
fill: 'blue',
left: 150,
top: 150
});
canvas.add(circle, triangle);
canvas.on('object:moving', function(e) {
var obj = e.target;
// if object is too big ignore
if (obj.currentHeight > obj.canvas.height - padding * 2 ||
obj.currentWidth > obj.canvas.width - padding * 2) {
return;
}
obj.setCoords();
// top-left corner
if (obj.getBoundingRect().top < padding ||
obj.getBoundingRect().left < padding) {
obj.top = Math.max(obj.top, obj.top - obj.getBoundingRect().top + padding);
obj.left = Math.max(obj.left, obj.left - obj.getBoundingRect().left + padding);
}
// bot-right corner
if (obj.getBoundingRect().top + obj.getBoundingRect().height > obj.canvas.height - padding ||
obj.getBoundingRect().left + obj.getBoundingRect().width > obj.canvas.width - padding) {
obj.top = Math.min(
obj.top,
obj.canvas.height - obj.getBoundingRect().height + obj.top - obj.getBoundingRect().top - padding);
obj.left = Math.min(
obj.left,
obj.canvas.width - obj.getBoundingRect().width + obj.left - obj.getBoundingRect().left - padding);
}
});
canvas {
border: 1px solid #dddddd;
margin-top: 10px;
border-radius: 3px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.20/fabric.min.js"></script>
<canvas id="c"></canvas>
In the first line, I've defined a variable “padding” that refers to the desired padding size. This way, if you want to change the padding later on, you don't have to change it in eight different places.
The padding variable used instead of 0 in the condition for the top left corner.
If the “top left” condition is true, the padding is added to the calculation for repositioning the object.
In the “bottom right” part, we are doing the opposite – we subtract the padding from the condition and subtract from the repositioning calculation.
You can also try the code in this fiddle: https://jsfiddle.net/pahund/hz7jLnme/
I have been trying to modify a code which scrolls the images horizontally. I want to scroll it vertically. I don't know JQuery at all. After trying for hours I couldn't find any way to sort this thing out. Can anyone help me out in this regard.
Heres the whole code.
$(function() {
$(window).load(function() {
var $gal = $("#propertyThumbnails"),
galW = $gal.outerWidth(true),
galSW = $gal[0].scrollWidth,
wDiff = (galSW / galW) - 1, // widths difference ratio
mPadd = 60, // Mousemove Padding
damp = 20, // Mousemove response softness
mX = 0, // Real mouse position
mX2 = 0, // Modified mouse position
posX = 0,
mmAA = galW - (mPadd * 2), // The mousemove available area
mmAAr = (galW / mmAA); // get available mousemove fidderence ratio
$gal.mousemove(function(e) {
mX = e.pageX - $(this).parent().offset().left - this.offsetLeft;
mX2 = Math.min(Math.max(0, mX - mPadd), mmAA) * mmAAr;
});
setInterval(function() {
posX += (mX2 - posX) / damp; // zeno's paradox equation "catching delay"
$gal.scrollLeft(posX * wDiff);
}, 10);
});
});
#parent {
position: relative;
margin: 0 auto;
width: 100%;
background: #ddd;
}
#propertyThumbnails {
position: relative;
overflow: hidden;
background: #444;
width: 100%;
white-space: nowrap;
}
#propertyThumbnails img {
vertical-align: middle;
display: inline;
margin-left: -4px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="parent">
<div id="propertyThumbnails">
<img src="http://placehold.it/1000x100" />
</div>
</div>
Heres the demo:
http://jsbin.com/alokat/1/edit?html,css,js,output
I changed the script as
$(function(){
$(window).load(function(){
var $gal = $("#propertyThumbnails"),
galW = $gal.outerHeight(true),
galSW = $gal[0].scrollHeight,
wDiff = (galSW/galW)-1, // widths difference ratio
mPadd = 60, // Mousemove Padding
damp = 20, // Mousemove response softness
mX = 0, // Real mouse position
mX2 = 0, // Modified mouse position
posX = 0,
mmAA = galW-(mPadd*2), // The mousemove available area
mmAAr = (galW/mmAA); // get available mousemove fidderence ratio
$gal.mousemove(function(e) {
mX = e.pageY - $(this).parent().offset().top - this.offsetTop;
mX2 = Math.min( Math.max(0, mX-mPadd), mmAA ) * mmAAr;
});
setInterval(function(){
posX += (mX2 - posX) / damp; // zeno's paradox equation "catching delay"
$gal.scrollTop(posX*wDiff);
}, 10);
});
});