I'm building an app that can design your own business card. I have to add an object to two canvases in a single click. Here are my codes:
$('#image-list').on('click','.image-option',function(e) {
var el = e.target;
/*temp code*/
var offset = 50;
var left = fabric.util.getRandomInt(0 + offset, 200 - offset);
var top = fabric.util.getRandomInt(0 + offset, 400 - offset);
var angle = fabric.util.getRandomInt(-20, 40);
var width = fabric.util.getRandomInt(30, 50);
var opacity = (function(min, max){ return Math.random() * (max - min) + min; })(0.5, 1);
var canvasObject;
// if ($('#flip').attr('data-facing') === 'front') {
// canvasObject = canvas;
// } else {
// canvasObject = canvas2;
// }
fabric.Image.fromURL(el.src, function(image) {
image.set({
left: left,
top: top,
angle: 0,
padding: 10,
cornersize: 10,
hasRotatingPoint:true
});
canvas.add(image);
canvas2.add(image);
});
})
The problem is, when I resized or move the image on the 'front canvas', it also renders the same way in the 'back canvas'. In my case, I don't want the object to be that way. So is there a way to prevent the obect 'mirroring' on the other canvas? Thanks.
You cannot add the same object to 2 canvases.
You have to create 2 objects.
Also take note that if you have an html image element on your page you do not need to load it from URL again. Is already loaded, so pass the image element to the constructor directly
$('#image-list').on('click','.image-option',function(e) {
var el = e.target;
/*temp code*/
var offset = 50;
var left = fabric.util.getRandomInt(0 + offset, 200 - offset);
var top = fabric.util.getRandomInt(0 + offset, 400 - offset);
var angle = fabric.util.getRandomInt(-20, 40);
var width = fabric.util.getRandomInt(30, 50);
var opacity = (function(min, max){ return Math.random() * (max - min) + min; })(0.5, 1);
var canvasObject;
// if ($('#flip').attr('data-facing') === 'front') {
// canvasObject = canvas;
// } else {
// canvasObject = canvas2;
// }
image = new fabric.Image(el, {
left: left,
top: top,
angle: 0,
padding: 10,
cornersize: 10,
hasRotatingPoint:true
});
image2 = new fabric.Image(el, {
left: left,
top: top,
angle: 0,
padding: 10,
cornersize: 10,
hasRotatingPoint:true
});
canvas.add(image);
canvas2.add(image2);
});
})
Related
I recreated the scrolling text box tutorial in my game. However, it is running a bit glitchy on mobile. For example, if I swipe up, the text first goes down for a second and then follows my finger up. You’ll see the problem if you open the tutorial on mobile. Any thoughts? I copied my code below.
var graphics = scene.make.graphics();
graphics.fillRect(x, y + 10, width, height - 20);
var mask = new Phaser.Display.Masks.GeometryMask(scene, graphics);
var text = scene.add.text(x + 20, y + 20, content, {
fontFamily: 'Assistant',
fontSize: '28px',
color: '#000000',
wordWrap: { width: width - 20 }
}).setOrigin(0);
text.setMask(mask);
var minY = height - text.height - 20;
if (text.height <= height - 20) {
minY = y + 20;
}
// The rectangle they can 'drag' within
var zone = scene.add.zone(x, y - 3, width, height + 6).setOrigin(0).setInteractive({useHandCursor: true});
zone.on('pointermove', function (pointer) {
if (pointer.isDown) {
text.y += (pointer.velocity.y / 10);
text.y = Phaser.Math.Clamp(text.y, minY, y + 20);
}
});
I had the same issue. My solution is using "pointer.y" instead of "pointer.velocity.y".
Here is my code:
var previousPointerPositionY;
var currentPointerPositionY;
zone.on('pointerdown', function (pointer) {
previousPointerPositionY = pointer.y;
});
zone.on('pointermove', function (pointer) {
if (pointer.isDown) {
currentPointerPositionY = pointer.y;
if(currentPointerPositionY > previousPointerPositionY){
text.y += 5;
} else if(currentPointerPositionY < previousPointerPositionY){
text.y -= 5;
}
previousPointerPositionY = currentPointerPositionY;
text.y = Phaser.Math.Clamp(text.y, -360, 150);
}
});
I'm a beginner in three.js. My task is to build a simple FPS game. I'm having many troubles with the gun and the bullets. When I press "spacebar" my weapon shoots but the problem is that the bullets go in the right direction only for a small part of the screen then they start to go in direction that are not the ones I want.
This is an example :
image1
image2
Here is the code I wrote for the bullet :
// SHOOT BULLET
for(var index=0; index<bullets.length; index+=1){
if( bullets[index] === undefined ) continue;
if( bullets[index].alive == false ){
bullets.splice(index,1);
continue;
}
bullets[index].position.add(bullets[index].velocity);
}
if(keyboard[32] && canShoot <= 0){ // spacebar key
// creates a bullet as a Mesh object
var bullet = new THREE.Mesh(
new THREE.SphereGeometry(0.2,8,8),
new THREE.MeshBasicMaterial({color:0x42FFFF})
);
// position the bullet to come from the player's weapon
bullet.position.set(
camera.position.x - 0.7*parseInt(-Math.cos(camera.rotation.z)),
camera.position.y - 0.3,
camera.position.z +1*parseInt(-Math.cos(camera.rotation.z))
);
// set the velocity of the bullet
bullet.velocity = new THREE.Vector3( (-mouse.x - Math.sin(camera.rotation.y + Math.PI/6) * 7),//*parseInt(-Math.cos(camera.rotation.z)) ,
mouse.y,
Math.cos(camera.rotation.y)*parseInt(-Math.cos(camera.rotation.z))
).normalize();
console.info(bullet.velocity);
// after 1000ms, set alive to false and remove from scene
// setting alive to false flags our update code to remove
// the bullet from the bullets array
bullet.alive = true;
setTimeout(function(){
bullet.alive = false;
scene.remove(bullet);
}, 1000);
// add to scene, array, and set the delay to 10 frames
bullets.push(bullet);
scene.add(bullet);
canShoot = 10;
}
if(canShoot > 0) canShoot -= 1;
A rough concept of how you can set direction and movement of bullets:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.01, 10000);
camera.position.set(0, 0, 1);
scene.add(camera);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var background = new THREE.Mesh(new THREE.SphereGeometry(1000, 90, 45), new THREE.MeshBasicMaterial({
color: "gray",
wireframe: true
}));
scene.add(background);
var weapon = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 5), new THREE.MeshBasicMaterial({
color: 0x5555ff
}));
weapon.position.set(2, -1, -2.5);
camera.add(weapon);
var emitter = new THREE.Object3D();
emitter.position.set(2, -1, -5);
camera.add(emitter);
var plasmaBalls = [];
window.addEventListener("mousedown", onMouseDown);
function onMouseDown() {
let plasmaBall = new THREE.Mesh(new THREE.SphereGeometry(0.5, 8, 4), new THREE.MeshBasicMaterial({
color: "aqua"
}));
plasmaBall.position.copy(emitter.getWorldPosition()); // start position - the tip of the weapon
plasmaBall.quaternion.copy(camera.quaternion); // apply camera's quaternion
scene.add(plasmaBall);
plasmaBalls.push(plasmaBall);
}
var speed = 50;
var clock = new THREE.Clock();
var delta = 0;
(function render() {
requestAnimationFrame(render);
delta = clock.getDelta();
plasmaBalls.forEach(b => {
b.translateZ(-speed * delta); // move along the local z-axis
});
renderer.render(scene, camera);
})()
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.115.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three#0.115.0/examples/js/controls/OrbitControls.js"></script>
I need in my application made in fabricJS, that when I modify an object with the scaling handlers, the scaling stays in 1, and that the size of the object takes the value of the height multiplied by scaleX. I can apply the same for the width. But I can not apply this in the images. How can I fix this?
I leave an example fiddle for a square, and an image, so that you can see the result.
The result in the image is similar to a crop, and I can not avoid this, because apparently, when I modify the container of the image, the _element property that also contains scaleX and scaleY does not change, so the image is not resized inside the container.
Here is jsFiddle.
var canvas = new fabric.Canvas('canvas');
var rect1 = new fabric.Rect({
width: 100,
height: 100,
left: 200,
top: 200,
angle: 0,
fill: 'rgba(0,0,255,1)',
originX: 'center',
originY: 'center'
});
canvas.add(rect1);
fabric.Image.fromURL('http://fabricjs.com/assets/pug_small.jpg', function(myImg) {
//i create an extra var for to change some image properties
var img1 = myImg.set({
left: 0,
top: 0,
width: 150,
height: 150
});
canvas.add(img1);
});
canvas.on('mouse:up', function(e) {
/*if(e.target != null){
//alert(e.target.width*e.target.scaleX);
}*/
//console.log(e.target);
canvas.getActiveObjects().forEach(function(obj) {
//if(obj.get('type')!='image'){
obj.set('width', obj.width * obj.scaleX, obj);
obj.set('height', obj.height * obj.scaleY, obj);
//if(obj.get('type')!='image'){
obj.scaleX = 1;
obj.scaleY = 1;
//}else{
// obj.scaleX = 1;
// obj.scaleY = 1;
// obj._element.scaleX = 1;
// obj._element.scaleY = 1;
// console.log("Width Contenedor Imagen: "+obj.width+" Width Imagen: "+obj._element.width);
// console.log("Scale Contenedor Imagen: "+obj.scaleX+" Scale Imagen: "+obj._element.scaleX);
//}
obj.setCoords();
//}
});
});
EDIT: New Discovery
Speaking of text, rect, images and polylines, it seems that I am modifying an external container, because the inner content remains intact. How can I modify the size of that let's say, inner container?
I have an algorithm for Floodfilling a canvas. Im trying to incorporate this with fabricJS. So here is the dilemna.... I create a fabric.Canvas(). Which creates a wrapper canvas and also an upper-canvas canvas. I click on the canvas to apply my Floodfill(). This works fine and applies my color. But as soon as i go to drag my canvas objects around, or add additional objects to the canvas, the color disappears and looks like it resets of sort.
Any idea why this is?
This happen because fabricjs wipe out all canvas every frame and redraw from its internal data.
I made a JSfiddle that implements Flood Fill for Fabric JS. Check it here: https://jsfiddle.net/av01d/dfvp9j2u/
/*
* FloodFill for fabric.js
* #author Arjan Haverkamp (av01d)
* #date October 2018
*/
var FloodFill = {
// Compare subsection of array1's values to array2's values, with an optional tolerance
withinTolerance: function(array1, offset, array2, tolerance)
{
var length = array2.length,
start = offset + length;
tolerance = tolerance || 0;
// Iterate (in reverse) the items being compared in each array, checking their values are
// within tolerance of each other
while(start-- && length--) {
if(Math.abs(array1[start] - array2[length]) > tolerance) {
return false;
}
}
return true;
},
// The actual flood fill implementation
fill: function(imageData, getPointOffsetFn, point, color, target, tolerance, width, height)
{
var directions = [[1, 0], [0, 1], [0, -1], [-1, 0]],
coords = [],
points = [point],
seen = {},
key,
x,
y,
offset,
i,
x2,
y2,
minX = -1,
maxX = -1,
minY = -1,
maxY = -1;
// Keep going while we have points to walk
while (!!(point = points.pop())) {
x = point.x;
y = point.y;
offset = getPointOffsetFn(x, y);
// Move to next point if this pixel isn't within tolerance of the color being filled
if (!FloodFill.withinTolerance(imageData, offset, target, tolerance)) {
continue;
}
if (x > maxX) { maxX = x; }
if (y > maxY) { maxY = y; }
if (x < minX || minX == -1) { minX = x; }
if (y < minY || minY == -1) { minY = y; }
// Update the pixel to the fill color and add neighbours onto stack to traverse
// the fill area
i = directions.length;
while (i--) {
// Use the same loop for setting RGBA as for checking the neighbouring pixels
if (i < 4) {
imageData[offset + i] = color[i];
coords[offset+i] = color[i];
}
// Get the new coordinate by adjusting x and y based on current step
x2 = x + directions[i][0];
y2 = y + directions[i][1];
key = x2 + ',' + y2;
// If new coordinate is out of bounds, or we've already added it, then skip to
// trying the next neighbour without adding this one
if (x2 < 0 || y2 < 0 || x2 >= width || y2 >= height || seen[key]) {
continue;
}
// Push neighbour onto points array to be processed, and tag as seen
points.push({ x: x2, y: y2 });
seen[key] = true;
}
}
return {
x: minX,
y: minY,
width: maxX-minX,
height: maxY-minY,
coords: coords
}
}
}; // End FloodFill
var fcanvas; // Fabric Canvas
var fillColor = '#f00';
var fillTolerance = 2;
function hexToRgb(hex, opacity) {
opacity = Math.round(opacity * 255) || 255;
hex = hex.replace('#', '');
var rgb = [], re = new RegExp('(.{' + hex.length/3 + '})', 'g');
hex.match(re).map(function(l) {
rgb.push(parseInt(hex.length % 2 ? l+l : l, 16));
});
return rgb.concat(opacity);
}
function floodFill(enable) {
if (!enable) {
fcanvas.off('mouse:down');
fcanvas.selection = true;
fcanvas.forEachObject(function(object){
object.selectable = true;
});
return;
}
fcanvas.deactivateAll().renderAll(); // Hide object handles!
fcanvas.selection = false;
fcanvas.forEachObject(function(object){
object.selectable = false;
});
fcanvas.on({
'mouse:down': function(e) {
var mouse = fcanvas.getPointer(e.e),
mouseX = Math.round(mouse.x), mouseY = Math.round(mouse.y),
canvas = fcanvas.lowerCanvasEl,
context = canvas.getContext('2d'),
parsedColor = hexToRgb(fillColor),
imageData = context.getImageData(0, 0, canvas.width, canvas.height),
getPointOffset = function(x,y) {
return 4 * (y * imageData.width + x)
},
targetOffset = getPointOffset(mouseX, mouseY),
target = imageData.data.slice(targetOffset, targetOffset + 4);
if (FloodFill.withinTolerance(target, 0, parsedColor, fillTolerance)) {
// Trying to fill something which is (essentially) the fill color
console.log('Ignore... same color')
return;
}
// Perform flood fill
var data = FloodFill.fill(
imageData.data,
getPointOffset,
{ x: mouseX, y: mouseY },
parsedColor,
target,
fillTolerance,
imageData.width,
imageData.height
);
if (0 == data.width || 0 == data.height) {
return;
}
var tmpCanvas = document.createElement('canvas'), tmpCtx = tmpCanvas.getContext('2d');
tmpCanvas.width = canvas.width;
tmpCanvas.height = canvas.height;
var palette = tmpCtx.getImageData(0, 0, tmpCanvas.width, tmpCanvas.height); // x, y, w, h
palette.data.set(new Uint8ClampedArray(data.coords)); // Assuming values 0..255, RGBA
tmpCtx.putImageData(palette, 0, 0); // Repost the data.
var imgData = tmpCtx.getImageData(data.x, data.y, data.width, data.height); // Get cropped image
tmpCanvas.width = data.width;
tmpCanvas.height = data.height;
tmpCtx.putImageData(imgData,0,0);
fcanvas.add(new fabric.Image(tmpCanvas, {
left: data.x,
top: data.y,
selectable: false
}))
}
});
}
$(function() {
// Init Fabric Canvas:
fcanvas = new fabric.Canvas('c', {
backgroundColor:'#fff',
enableRetinaScaling: false
});
// Add some demo-shapes:
fcanvas.add(new fabric.Circle({
radius: 80,
fill: false,
left: 100,
top: 100,
stroke: '#000',
strokeWidth: 2
}));
fcanvas.add(new fabric.Triangle({
width: 120,
height: 160,
left: 50,
top: 50,
stroke: '#000',
fill: '#00f',
strokeWidth: 2
}));
fcanvas.add(new fabric.Rect({
width: 120,
height: 160,
left: 150,
top: 50,
fill: 'red',
stroke: '#000',
strokeWidth: 2
}));
fcanvas.add(new fabric.Rect({
width: 200,
height: 120,
left: 200,
top: 120,
fill: 'green',
stroke: '#000',
strokeWidth: 2
}));
/* Images work very well too. Make sure they're CORS
enabled though! */
var img = new Image();
img.crossOrigin = 'anonymous';
img.onload = function() {
fcanvas.add(new fabric.Image(img, {
left: 300,
top: 100,
angle: 30,
}));
}
img.src = 'http://misc.avoid.org/chip.png';
});
In fabricjs, I want to create a scene in which the object under the mouse rises to the top of the scene in z-index, then once the mouse leaves that object, it goes back to the z-index where it came from. One cannot set object.zindex (which would be nice). Instead, I'm using a placeholder object which is put into the object list at the old position, and then the old object is put back in the position where it was in the list using canvas.insertAt. However this is not working.
See http://jsfiddle.net/rFSEV/ for the status of this.
var canvasS = new fabric.Canvas('canvasS', { renderOnAddition: false, hoverCursor: 'pointer', selection: false });
var bars = {}; //storage for bars (bar number indexed by group object)
var selectedBar = null; //selected bar (group object)
var placeholder = new fabric.Text("XXXXX", { fontSize: 12 });
//pass null or a bar
function selectBar(bar) {
if (selectedBar) {
//remove the old topmost bar and put it back in the right zindex
//PROBLEM: It doesn't go back; it stays at the same zindex
selectedBar.remove();
canvasS.insertAt(selectedBar, selectedBar.XZIndex, true);
selectedBar = null;
}
if (bar) {
//put a placeholder object ("XXX" for now) in the position
//where the bar was, and put the bar in the top position
//so it shows topmost
selectedBar = bar;
canvasS.insertAt(placeholder, selectedBar.XZIndex, true);
canvasS.add(bar);
canvasS.renderAll();
}
}
canvasS.on({
'mouse:move': function(e) {
//hook up dynamic zorder
if (!e.target) return;
if (bars[e.target])
selectBar(e.target);
else
selectBar(null);
},
});
var objcount = canvasS.getObjects().length;
//create bars
for (var i = 0; i < 20; ++i) {
var rect = new fabric.Rect({
left: 0,
top: 0,
rx: 3,
ry: 3,
stroke: 'red',
width: 200,
height: 25
});
rect.setGradientFill({
x1: 0,
y1: 0,
x2: 0,
y2: rect.height,
colorStops: {
0: '#080',
1: '#fff'
}
});
var text = new fabric.Text("Bar number " + (i+1), {
fontSize: 12
});
var group = new fabric.Group([ rect, text ], {
left: i + 101,
top: i * 4 + 26
});
group.hasControls = group.hasBorders = false;
//our properties (not part of fabric)
group.XBar = rect;
group.XZIndex = objcount++;
canvasS.add(group);
bars[group] = i;
}
canvasS.renderAll();
Since fabric.js version 1.1.4 a new method for zIndex manipulation is available:
canvas.moveTo(object, index);
object.moveTo(index);
I think this is helpful for your use case. I've updated your jsfiddle - i hope this is what you want:
jsfiddle
Also make sure you change z-index AFTER adding object to canvas.
So code will looks like:
canvas.add(object);
canvas.moveTo(object, index);
Otherwise fabricjs don`t care about z-indexes you setup.
After I added a line object, I was make the line appear under the object using:
canvas.add(line);
canvas.sendToBack(line);
Other options are
canvas.sendBackwards
canvas.sendToBack
canvas.bringForward
canvas.bringToFront
see: https://github.com/fabricjs/fabric.js/issues/135
You can modify your _chooseObjectsToRender method to have the following change at the end of it, and you'll be able to achieve css-style zIndexing.
objsToRender = objsToRender.sort(function(a, b) {
var sortValue = 0, az = a.zIndex || 0, bz = b.zIndex || 0;
if (az < bz) {
sortValue = -1;
}
else if (az > bz) {
sortValue = 1;
}
return sortValue;
});
https://github.com/fabricjs/fabric.js/pull/5088/files
You can use these two functions to get z-index of a fabric object and modify an object's z-index, since there is not specific method to modify z-index by object index :
fabric.Object.prototype.getZIndex = function() {
return this.canvas.getObjects().indexOf(this);
}
fabric.Canvas.prototype.moveToLayer = function(object,position) {
while(object.getZIndex() > position) {
this.sendBackwards(object);
}
}