I want to make sure that my instance of fabric.Image is always visible.
It doesn't need to be 100% visible, but a portion of it should always be seen. I've seen a few other questions that are similar and I've based my solution on them, but I haven't found anything that completely solves the problem.
My current solution works when no rotation (angles) have been applied to the image. If the angle is at 0 then this solution is perfect, but once the angle start changing I'm having problems.
this.canvas.on('object:moving', function (e) {
var obj = e.target;
obj.setCoords();
var boundingRect = obj.getBoundingRect();
var max_pad_left_over_width = 50;//obj.currentWidth * .08;
var max_pad_left_over_height = 50;//obj.currentHeight * .08;
var max_top_pos = -(obj.currentHeight - max_pad_left_over_height);
var max_bottom_pos = obj.canvas.height - max_pad_left_over_height;
var max_left_pos = -(obj.currentWidth - max_pad_left_over_width);
var max_right_pos = obj.canvas.width - max_pad_left_over_width;
if(boundingRect.top < max_top_pos) {
obj.setTop(max_top_pos);
}
if(boundingRect.left < max_left_pos){
obj.setLeft(max_left_pos);
}
if(boundingRect.top > max_bottom_pos) {
obj.setTop(max_bottom_pos);
}
if(boundingRect.left > max_right_pos) {
obj.setLeft(max_right_pos);
}
});
I've created an example: https://jsfiddle.net/krio/wg0aL8ef/24/
Notice how when no rotation (angle) is applied you cannot force the image to leave the visible canvas. Now, add some rotation to the image and move it around. How can I get the rotated image to also always stay within view? Thanks.
Fabric.js provides with two object properties isContainedWithinRect and intersectsWithRect which we can use to solve the problem.
An object should not be allowed to go outside the canvas boundaries. This can be achieved if we somehow manage to make sure that the object is either inside the canvas, or at least intersects with the canvas boundaries.
For this case, since you want some portion of the image inside the canvas, we will take a rect smaller than the canvas instead of canvas boundaries to be the containing rect of the image.
Using the properties mentioned to make sure that the above two conditions are satisfied after any movement. Here's how the code looks like:
var canvas = new fabric.Canvas('c');
var imgElement = document.getElementById('my-img');
var imgInstance = new fabric.Image(imgElement, {
top: 0,
left: 0
});
imgInstance.setScaleX(0.6);
imgInstance.setScaleY(0.6);
canvas.add(imgInstance);
var prevLeft = 0,
prevTop = 0;
var max_pad_left_over_width = imgInstance.currentWidth * .08;
var max_pad_left_over_height = imgInstance.currentHeight * .08;
var max_top_pos = max_pad_left_over_height;
var max_bottom_pos = imgInstance.canvas.height - max_pad_left_over_height;
var max_left_pos = max_pad_left_over_width;
var max_right_pos = imgInstance.canvas.width - max_pad_left_over_width;
var pointTL = new fabric.Point(max_left_pos, max_top_pos);
var pointBR = new fabric.Point(max_right_pos, max_bottom_pos);
canvas.on('object:moving', function(e) {
var obj = e.target;
obj.setCoords();
if (!obj.intersectsWithRect(pointTL, pointBR) && !obj.isContainedWithinRect(pointTL, pointBR)) {
obj.setTop(prevTop);
obj.setLeft(prevLeft);
}
prevLeft = obj.left;
prevTop = obj.top;
});
Here's the link to the fiddle: https://jsfiddle.net/1ghvjxbk/ and documentation where these properties are mentioned.
Related
I am trying to automate inDesign to create text frames from a JSON file, where every record shall be placed in a text frame, which is right below the foregoing record.
var tweets_json = '[ {"name":"record_1"}, {"name":"record_2"}, ... ]'; // I have not yet figured out how to import external JSON files, so tor testing this is my current way to go
var tweets = eval("(" + tweets_json + ")");
for ( var t = 0; t < tweets.length; t++ ) {
var newTextFrame = page.textFrames.add();
newTextFrame.textFramePreferences.autoSizingType = AutoSizingTypeEnum.HEIGHT_ONLY;
var tweet = tweets[ t ];
newTextFrame.parentStory.texts.item(0).applyParagraphStyle(styleContent, true);
newTextFrame.geometricBounds = [y1,x1,y2,x2]; // Ignore values
newTextFrame.contents = tweet.name;
}
I started inDesign scripting a few days ago and the documentation is hard to find the solution i am looking for, especially with my basic JavaScript knowledge, so any help is appreciated! Thank You!
It could be something like this:
var doc = app.activeDocument;
var page = doc.pages[0];
// get JSON data (just as an example)
var tweets_json = '[ {"name":"line1 line2"}, {"name":"line3"}, {"name":"line4, line5"}]';
var tweets = eval("(" + tweets_json + ")");
// set geometric bounds for the first text frame
var x = 0, y = 0; // coordinates of top a left corner of the text frame
var width = 15; // width of the text frame
var min_heigth = 20; // minimal heigth of the text frame
var bottom = min_heigth; // bottom edge of the text frame
var bounds = [y, x, bottom, width];
for (var t = 0; t < tweets.length; t++) {
var tweet = tweets[t];
// create the a new text frame and set its geometric bounds
var newTextFrame = page.textFrames.add();
newTextFrame.geometricBounds = bounds;
// fill the box with a text and apply the style
newTextFrame.contents = tweet.name;
newTextFrame.parentStory.texts[0].applyParagraphStyle(styleContent, true);
// resize the created text box with auto sizing
newTextFrame.textFramePreferences.autoSizingReferencePoint = AutoSizingReferenceEnum.TOP_CENTER_POINT;
newTextFrame.textFramePreferences.autoSizingType = AutoSizingTypeEnum.HEIGHT_ONLY;
// set the new geometric bounds for the next text frame
bounds = newTextFrame.geometricBounds; // get the bounds of the created text frame
bottom = bounds[2]; // get the new bottom edge
bounds[0] = bottom; // set the top edge for the next text frame
bounds[2] = bounds[2] + min_heigth; // set the bottom edge for the next text frame
}
Result:
The code a bit verbose, for educational purposes. Actually it can be way shorter.
I am making a battleship game with polar coordinates. After the user chooses two points, a battleship should be drawn in the middle. My Battleship constructor looks like this:
function Battleship(size, location, source){
this.size = size;
//initializing the image
this.image = new Image();
this.image.src = source;
this.getMiddlePoint = function(){
//get midpoint of ship
...
}
this.distanceBetween = function(t1, t2){
//dist between two points
}
this.display = function(){
var point = [this.radius];
point.push(this.getMiddlePoint());
point = polarToReal(point[0], point[1] * Math.PI / 12);
//now point has canvas coordinates of midpoint
var width = this.distanceBetween(this.info[0][0], this.info[this.info.length-1][0]);
var ratio = this.image.width / width;
ctx.drawImage(this.image, point[0] - width/2, point[1] - this.image.height / ratio / 2, width, this.image.height / ratio);
//draws the image
}
}
The display method of each ship gets called at a certain point (after the user has chosen the location). For some reason, the images do not show the first time I do this, but when I run this code at the very end:
for(var i = 0; i<playerMap.ships.length; i++){
playerMap.ships[i].display();
}
All ships are displayed correctly (not aligned well, but they are displayed). I think there is a problem with loading the images. I am not sure how to fix this. I tried using image.onload but I never got that to work. I also tried something like this:
var loadImage = function (url, ctx) {
var img = new Image();
img.src = url
img.onload = function () {
ctx.drawImage(img, 0, 0);
}
}
but the same problem kept happening. Please help me fix this problem. Here is the game in its current condition. If you place ships, nothing happens, but after you place 5 (or 10) ships, they suddenly all load.
EDIT:
I solved the problem by globally defining the images. This is still very bad practice, since I wanted this to be in the battleship object. This is my (temporary) solution:
var sub = [];
for(var i = 1; i<5; i++){
sub[i] = new Image();
sub[i].src = "/img/ships/battleship_"+i+".png";
}
I had drawn an animation in canvas like this and rendered a map using openlayers4. I want to add this canvas to the map[openlayers canvas] in next step.
I had used ol.source.ImageCanvas add a boundary to openlayers, so I try to add the canvas with animation using ImageCanvas, but failed.
What's more, openlayers API said ol.source.ImageCanvas method only the image canvas can be added. I didn't know whether the animate canvas so does.
Should I insit on using ImageCanvas method or try others?
Can someone give me an example if I abandon the ImageCanvas method?
After some tries, I got a solution! Haha!
First: the ol.source.ImageCanvas can still use, but you will get a stopped animate just like a screenshot.
Second: must know the ol.map.render() in openlayers3 or openlayers4, whose description is:
Request a map rendering (at the next animation frame).
Thus, you can use it to refresh the map and get the next animation of canvas.
The following is snippets of my code:
var topoCanvas = function(extent, resolution, pixelRatio, size, projection) {
// topo features;
var features = topojson.feature(tokyo, tokyo.objects.counties);
var canvasWidth = size[0];
var canvasHeight = size[1];
var canvas = d3.select(document.createElement('canvas'));
canvas.attr('width', canvasWidth).attr('height', canvasHeight);
var context = canvas.node().getContext('2d');
var d3Projection = d3.geo.mercator().scale(1).translate([0, 0]);
var d3Path = d3.geo.path().projection(d3Projection);
var pixelBounds = d3Path.bounds(features);
var pixelBoundsWidth = pixelBounds[1][0] - pixelBounds[0][0];
var pixelBoundsHeight = pixelBounds[1][1] - pixelBounds[0][1];
var geoBounds = d3.geo.bounds(features);
var geoBoundsLeftBottom = ol.proj.transform(geoBounds[0], 'EPSG:4326', projection);
var geoBoundsRightTop = ol.proj.transform(geoBounds[1], 'EPSG:4326', projection);
var geoBoundsWidth = geoBoundsRightTop[0] - geoBoundsLeftBottom[0];
if (geoBoundsWidth < 0) {
geoBoundsWidth += ol.extent.getWidth(projection.getExtent());
}
var geoBoundsHeight = geoBoundsRightTop[1] - geoBoundsLeftBottom[1];
var widthResolution = geoBoundsWidth / pixelBoundsWidth;
var heightResolution = geoBoundsHeight / pixelBoundsHeight;
var r = Math.max(widthResolution, heightResolution);
var scale = r / (resolution / pixelRatio);
var center = ol.proj.transform(ol.extent.getCenter(extent), projection, 'EPSG:4326');
d3Projection.scale(scale).center(center).translate([canvasWidth / 2, canvasHeight / 2]);
d3Path = d3Path.projection(d3Projection).context(context);
d3Path(features);
context.stroke();
// above code is add a topoJson boundary to canvas
// below code is add an animation to canvas
var settings = createSettings(tokyo, {
width: canvasWidth,
height: canvasHeight
});
// reset the projection and bounds for animation canvas
settings.projection = d3Projection;
settings.bounds = geoBounds;
var mesh = buildMeshes(tokyo, settings);
when(render(settings, mesh, {
width: canvasWidth,
height: canvasHeight
})).then(function(masks) {
when(interpolateField(stations, data, settings, masks)).then(function(field) {
// wind moving animation
animate(settings, field, canvas);
// refresh the map to get animation
window.setInterval(function() {
map.render();
}, 50);
});
});
return canvas[0][0];
}
when i am trying to rotate the path it is displaying same item two times. can some one help me to prevent the item to display twice.
var center1 = ball1.position;
var center2 = ball2.position;
// Create a triangle shaped path
var triangle = new Path.RegularPolygon(new Point(80, 70), 3, 12);
triangle.id = Math.random();
triangle.fillColor = '#fff';
//triangle.selected = true;
triangle.strokeColor = '#00304A';
triangle.strokeWidth = 2;
triangle.position = path.position; **
var arrowAngle = 90 + ((center2.subtract(center1)).angle); ** triangle.rotation = arrowAngle;
I'm not sure why you're getting two triangles (there isn't enough code for me to see what the reason for that is) but the main problem is that rotation is a read-only property - you cannot set it. Try using rotate().
http://paperjs.org/reference/path/#rotate-angle
I'm really struggling with the changing of my canvas drawn image so I thought I would see if anyone could assist me on here or offer advice.
I've drawn a static flag in canvas, and I've also drawn a waving flag. I'm trying to get this flag to wave on mouseover.
I initially thought that I was going to have to create two separate files, one for the static and one for the waving aspect. Then save each of them as a jpg/gif image using window.location = canvas.toDataURL("image/");.
But I've just discovered that you can apparently do this all in the same file via jquery/hover. Which seems a lot simpler and a more efficient way of doing it.
Here is the code for the waving flag:
window.onload = function(){
var flag = document.getElementById('banglaFlag');
banglaStatic( flag, 320 );
var timer = banglaWave( flag, 30, 15, 200, 200 );
};
function banglaStatic( canvas, width ){
//Drawing the Bangladesh flag.
//Declaring variables that regard width and height of the canvas.
//Variables C to L are needed for the waving function.
var a = width / 1.9;
var b = 200;
var c = 7*a/13;
var l = a / 13;
canvas.width = b;
canvas.height = a;
var ctx = canvas.getContext('2d');
var radius = 45;
};
function banglaWave( canvas, wavelength, amplitude, period, shading ){
var fps = 30;
var ctx = canvas.getContext('2d');
var w = canvas.width, h = canvas.height;
var od = ctx.getImageData(0,0,w,h).data;
// var ct = 0, st=new Date;
return setInterval(function(){
var id = ctx.getImageData(0,0,w,h);
var d = id.data;
var now = (new Date)/period;
for (var y=0;y<h;++y){
var lastO=0,shade=0;
for (var x=0;x<w;++x){
var px = (y*w + x)*4;
var o = Math.sin(x/wavelength-now)*amplitude*x/w;
var opx = ((y+o<<0)*w + x)*4;
shade = (o-lastO)*shading;
d[px ] = od[opx ]+shade;
d[px+1] = od[opx+1]+shade;
d[px+2] = od[opx+2]+shade;
d[px+3] = od[opx+3];
lastO = o;
}
}
ctx.putImageData(id,0,0);
// if ((++ct)%100 == 0) console.log( 1000 * ct / (new Date - st));
},1000/fps);
}
Thanks in advance for any advice/assistance.
I am not sure where your problem is. I did not see any event handling code, so I assume that's your question:
Define a function to "handle the mouse event". For example, if you want to move the flag when the user moves the mouse over it, define something like:
function mouseMove(event) {
var mouseX,
mouseY;
event.preventDefault(); // stops browser to do what it normally does
// determine where mouse is
mouseX = event.pageX;
mouseY = event.pageY;
// do something useful, e.g. change the flag to waving when mouse is over flag
}
Then, register this function to be called when the mouse moves:
canvas.addEventListener("mousemove", mouseMove, false);
canvas is the canvas you paint the flag on, "mousemove" is the name of the event (many more exist, such as "mousedown", "mouseup", "mouseout" (leaving canvas), "mousewheel", etc.), mouseMove is the name of your function (the event handler, as it's called).
Events are a little different from browser to browser (and even browser version), so you might need to implement different event handler if you need it across browsers.
Hoping this helped...
canvas is like a sheet. there is no any object on which you can hover.
for doing what you wanted to do is just,bound an area on the flag,
follow the 'virtualnobi' answer and calculate if mouse co-ordinate falls on that region,
if true do what ever you want.
like
if (mouseX<100 && mouseX>0 && mouseY>0 && mouseY<100){
//animate the flag
}
use mouseX=event.clientX;
mouseY=event.clientY;
bounded area is x=(0,100) , y=(0,100) here.