Need to check if user modified image using scale (controls on corners which resizes object proportionally) or stretching (using controls top center/bottom/left/right)
canvas[index].on('object:modified', function (options) {
...
How can i check this?
You can check using scaleX and scaleY of object. If it is corners than both scaleX and scaleY will change, if left/right only scaleX and for top/bottom scaleY will change.
DEMO
var canvas = new fabric.Canvas('canvas');
canvas.add(new fabric.Circle({
radius:50,
left:100,
top:100
}));
canvas.on('mouse:down',onMouseDown);
canvas.on('mouse:move',onMouseMove);
canvas.on('mouse:up',onMouseUp);
var mouseDown, target, originalState = {};
function onMouseDown(options){
var object = options.target;
if(!object) return;
mouseDown = true;
target = object;
originalState.scaleX = object.scaleX;
originalState.scaleY = object.scaleY;
}
function onMouseMove(options){
if(!(mouseDown || target)) return;
if(originalState.scaleX != target.scaleX && originalState.scaleY != target.scaleY){
console.log('scale using corners');
}
else if(originalState.scaleX != target.scaleX){
console.log('scale using left and right');
}
else if(originalState.scaleY != target.scaleY){
console.log('scale using top and bottom');
}
}
function onMouseUp(options){
mouseDown = false;
originalState = {};
target = null;
}
canvas{
border : 2px solid #000;
}
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id='canvas' width=400 height=400></canvas>
Related
I have a canvas. The users are able to draw rectangles in it using fabricjs. The second feature is that you are able to move around in the canvas (called panning). When I am in the drawing mode and than start to pan around, the rectangle disappears if I am moving the top left corner of the rectangle out of the canvas. If you move the viewport, so that the top left corner is in the canvas back again, the rectangle becomes visible again. If you move other corners of the rectangle out of the canvas, the rectangle stays visible.
Is there a way to prevent that behavior?
Steps to reproduce:
use snippet below
click the "rect" button
hold the left mouse key and draw a new rectangle (keep the mouse key pressed) - this will create a yellow rect
press and hold the "alt" key on your keyboard and move the mouse (the mouse key is still pressed) - you are panning around now
move the view, so that the top left corner of the rect will be outside the canvas - the yellow rect will disappear
Goal:
The rect will stay visible even if I am moving the top left corner out of the canvas.
(function() {
var drawMode = false
var isDrawing = false
var isPanning = false
document.getElementById("rect").onclick = function() {
drawMode = !drawMode
document.getElementById("rect").style["background-color"] = drawMode ? "lightblue" : "lightgrey"
}
var canvas = new fabric.Canvas("canvas")
canvas.on("mouse:down", function(opt) {
var evt = opt.e
this.selection = false
if (drawMode) {
isDrawing = true
this.startPosX = evt.clientX
this.startPosY = evt.clientY
this.drawRect = new fabric.Rect({
left: evt.clientX,
top: evt.clientY,
fill: "yellow",
})
canvas.add(this.drawRect)
} else if (evt.altKey === true) {
isPanning = true
this.lastPosX = evt.clientX
this.lastPosY = evt.clientY
}
})
canvas.on("mouse:move", function(opt) {
var evt = opt.e
if (evt.altKey === true && isPanning) {
var vpt = this.viewportTransform
if (this.lastPosX) vpt[4] += evt.clientX - this.lastPosX
if (this.lastPosY) vpt[5] += evt.clientY - this.lastPosY
this.requestRenderAll()
this.lastPosX = evt.clientX
this.lastPosY = evt.clientY
} else if (isDrawing) {
this.drawRect.set({
width: evt.clientX - this.startPosX,
height: evt.clientY - this.startPosY,
})
canvas.renderAll()
}
})
canvas.on("mouse:up", function(opt) {
// on mouse up we want to recalculate new interaction
// for all objects, so we call setViewportTransform
var evt = opt.e
this.setViewportTransform(this.viewportTransform)
isPanning = false
this.selection = true
if (isDrawing) {
this.drawRect.set({
width: evt.clientX - this.startPosX,
height: evt.clientY - this.startPosY,
fill: "orange",
})
isDrawing = false
}
})
document.addEventListener("keydown", function(opt) {
if (opt.key === "Alt") {
if (!isPanning) isPanning = true
if (drawMode) drawMode = false
}
})
document.addEventListener("keyup", function(opt) {
if (opt.key === "Alt") {
if (isPanning) isPanning = false
}
})
})()
<html>
<body style="margin: 0">
<canvas id="canvas" width=500 height=300 style="border: 1px solid blue"></canvas>
<h3>Tools</h3>
<button id="rect" style="background-color: lightgrey">rect</button>
<h3>Features</h3>
<ul>
<li>draw rectangle (activate draw mode with above button</li>
<li>move around (need to hold alt-key)</li>
</ul>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.4.0/fabric.min.js"></script>
</body>
</html>
Previously I was working on Phaser 2 but now I need to switch to Phaser 3.
I tried to make the canvas responsive with ScaleManager but it is not working.
I think some of the methods changed but I didn't find any help to rescale the stage full screen.
var bSize = {
bWidth: window.innerWidth ||
root.clientWidth ||
body.clientWidth,
bHeight: window.innerHeight ||
root.clientHeight ||
body.clientHeight,
};
var game;
var canvas = document.getElementById("canvas");
function create() {
// Scaling options
game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
// Have the game centered horizontally
game.scale.pageAlignHorizontally = true;
// And vertically
game.scale.pageAlignVertically = true;
// Screen size will be set automatically
game.scale.setScreenSize(true);
}
window.onload = function() {
// Create game canvas and run some blocks
game = new Phaser.Game(
bSize.bWidth, //is the width of your canvas i guess
bSize.bHeight, //is the height of your canvas i guess
Phaser.AUTO,
'frame', { create: create });
canvas.style.position = "fixed";
canvas.style.left = 0;
canvas.style.top = 0;
}
Since v3.16.0, use the Scale Manager. For short:
var config = {
type: Phaser.AUTO,
scale: {
mode: Phaser.Scale.FIT,
parent: 'phaser-example',
autoCenter: Phaser.Scale.CENTER_BOTH,
width: 800,
height: 600
},
//... other settings
scene: GameScene
};
var game = new Phaser.Game(config);
Here is the full code and here are some useful examples using the Scale Manager.
There isn't a scale manager for Phaser 3 yet but it's in development. For now I suggest following this tutorial. It basically centres the canvas with some CSS, then calls a resize function that handles maintaining the game ratio when the resize event is emitted by the window.
Here is the code used in the tutorial linked above:
The css:
canvas {
display: block;
margin: 0;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
The resize function:
function resize() {
var canvas = document.querySelector("canvas");
var windowWidth = window.innerWidth;
var windowHeight = window.innerHeight;
var windowRatio = windowWidth / windowHeight;
var gameRatio = game.config.width / game.config.height;
if(windowRatio < gameRatio){
canvas.style.width = windowWidth + "px";
canvas.style.height = (windowWidth / gameRatio) + "px";
}
else {
canvas.style.width = (windowHeight * gameRatio) + "px";
canvas.style.height = windowHeight + "px";
}
}
Then:
window.onload = function() {
//Game config here
var config = {...};
var game = new Phaser.Game(config);
resize();
window.addEventListener("resize", resize, false);
}
Try adding max-width to canvas css and then max-height based on aspect ratio. E.g:
canvas {
display: block;
margin: 0;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
max-width: 100%;
max-height: 50vw;
}
Scale manager will handle assets(Images,Sprites,..,...) positions and size sometime?
So adding an SVG to the canvas as follows:
// load the svg
fabric.loadSVGFromURL(self.currentDraggedIcon, function(objects, d) {
var iconGroup = fabric.util.groupSVGElements(objects, d);
iconGroup.set({
left: e.layerX,
top: e.layerY,
width: d.width,
height: d.height,
lockUniScaling: true,
// scaleY:self.currentObjectDesigner.scaleFactor,
// scaleX:self.currentObjectDesigner.scaleFactor,
dtype: 'UserIcon'
});
self.currentObjectDesigner.fabric.add(iconGroup);
self.currentObjectDesigner.fabric.bringToFront(iconGroup);
self.currentObjectDesigner.fabric.renderAll();
});
Later on there is a button to say change the paths colour to red inside the group, the code to do this is:
for (var i = 0; i < self.currentObjectDesigner.selectedObject._objects.length; i++) {
if (self.currentObjectDesigner.selectedObject.item(i).fill == findColor || self.currentObjectDesigner.selectedObject.item(i).fill == findColorAlt) {
self.currentObjectDesigner.selectedObject.item(i).fill = color;
}
}
self.currentObjectDesigner.selectedObject.addWithUpdate();
This works perfectly fine if the SVG has multiple paths, but when only a single path exists the _objects property doesn't exist so we are unable to perform the loop and item(i) part to set the fill on the path.
Question is: How do we now set a fill when _objects doesn't exist and the item() method doesn't exist because it's just a single path? - I.e it's not a group.
If more than one path present groupSVGElements returns group objects else returns a single object.
DEMO
var canvas = new fabric.Canvas('c');
function loadSvg(url, left, top) {
fabric.loadSVGFromURL(url, function(objects, d) {
var iconGroup = fabric.util.groupSVGElements(objects, d);
//for more than one path
iconGroup.set({
left: left,
top: top
})
if (iconGroup.type == 'group') {
//do your logic for group object
iconGroup.item(0).fill = 'yellow';
iconGroup.addWithUpdate();
} else {
iconGroup.fill = 'red';
}
iconGroup.set({
scaleX: 150 / iconGroup.width,
scaleY: 150 / iconGroup.height,
})
canvas.add(iconGroup);
}, function() {}, {
crossOrigin: 'anonymous'
});
}
loadSvg('https://upload.wikimedia.org/wikipedia/commons/2/22/Wikimapia_logotype.svg', 10, 20);
loadSvg('https://upload.wikimedia.org/wikipedia/commons/a/a0/Circle_-_black_simple.svg', 200, 50);
canvas{
border:2px solid #000;
}
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id='c' width=400 height=400></canvas>
I'm trying to load a .svg asset into my three.js scene, as a flat vector layer; I found this example with SVGLoader and SVGRenderer from another post, but I can't make it work.
The svg loaded is stuck in 2d space and not responding to camera movement, I can't access its position.
I tried to switch to WebGLRenderer, but the svg doesn’t get loaded.
The option of loading it as sprite would be good, but I would want the sprite to not face the camera and stay still in 3d space.
var svgManager = new THREE.SVGLoader();
var url = 'https://upload.wikimedia.org/wikipedia/commons/f/f7/Europe_laea_location_map.svg';
function svg_loading_done_callback(doc) {
init();
svg(new THREE.SVGObject(doc));
ico();
animate();
};
svgManager.load(url,
svg_loading_done_callback,
function() {
console.log("Loading SVG...");
},
function() {
console.log("Error loading SVG!");
});
var AMOUNT = 100;
var container, camera, scene, renderer;
function init() {
scene = new THREE.Scene();
renderer = new THREE.SVGRenderer();
renderer.setClearColor(0x00ff00);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
var container = document.getElementById('container');
container.appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.z = 1100;
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableZoom = true;
window.addEventListener('resize', onWindowResize, false);
}
function svg(svgObject) {
svgObject.position.x = 510;
svgObject.position.y = -110;
svgObject.position.z = 0;
scene.add(svgObject);
}
function ico() {
geometry = new THREE.IcosahedronGeometry(100, 1)
material = new THREE.MeshNormalMaterial({});
ico = new THREE.Mesh(geometry, material);
ico.position.y = -300;
scene.add(ico);
ico2 = new THREE.Mesh(geometry, material);
ico2.position.y = 500;
ico2.position.x = -500;
ico2.position.z = -50;
scene.add(ico2);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
controls.update;
render();
}
function render() {
renderer.render(scene, camera);
}
body {
margin: 0;
}
canvas {
width: 100%;
height: 100%
}
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<div id="container"></div>
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/renderers/SVGRenderer.js"></script>
<script src="https://threejs.org/examples/js/renderers/Projector.js"></script>
<script src="https://threejs.org/examples/js/loaders/SVGLoader.js"></script>
<script src="https://threejs.org/examples/js/libs/stats.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
The SVGLoader and SVGRenderer are two different things. The first loads an SVG file and converts it to three.js shapes (albeit with some limitations, i.e. can read very simple SVGs, does not render strokes but only filled objects, etc), while the latter renders three.js primitives using SVG elements instead of WebGL. In a sense, they are opposites of each other.
So, first of all, you'd need to use the WebGLRenderer for your case.
Then, you need to change the SVG loading callback. It receives an array of paths with which you can render the SVG.
See the changes in functions svg_loading_done_callback, init and svg, and run it in JSFiddle:
var svgManager = new THREE.SVGLoader();
var url = 'https://upload.wikimedia.org/wikipedia/commons/f/f7/Europe_laea_location_map.svg';
function svg_loading_done_callback(paths) {
init();
svg(paths);
ico();
animate();
};
svgManager.load(url,
svg_loading_done_callback,
function() {
console.log("Loading SVG...");
},
function() {
console.log("Error loading SVG!");
});
var AMOUNT = 100;
var container, camera, scene, renderer;
function init() {
scene = new THREE.Scene();
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0x00ff00);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
var container = document.getElementById('container');
container.appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.z = 1100;
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableZoom = true;
window.addEventListener('resize', onWindowResize, false);
}
function svg(paths) {
var group = new THREE.Group();
group.position.x = 510;
group.position.y = -110;
group.position.z = 0;
for ( var i = 0; i < paths.length; i ++ ) {
var path = paths[ i ];
var material = new THREE.MeshBasicMaterial( {
color: path.color,
side: THREE.DoubleSide,
depthWrite: false
} );
var shapes = path.toShapes( true );
for ( var j = 0; j < shapes.length; j ++ ) {
var shape = shapes[ j ];
var geometry = new THREE.ShapeBufferGeometry( shape );
var mesh = new THREE.Mesh( geometry, material );
group.add( mesh );
}
}
scene.add( group );
}
function ico() {
geometry = new THREE.IcosahedronGeometry(100, 1)
material = new THREE.MeshNormalMaterial({});
ico = new THREE.Mesh(geometry, material);
ico.position.y = -300;
scene.add(ico);
ico2 = new THREE.Mesh(geometry, material);
ico2.position.y = 500;
ico2.position.x = -500;
ico2.position.z = -50;
scene.add(ico2);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
controls.update;
render();
}
function render() {
renderer.render(scene, camera);
}
PS: Check the SVG Loader to see what it's able to parse
In Phaser (2.4.x), I'm drawing a circle around a sprite when it is dragged:
function dragStart(sprite, pointer, dragX, dragY) {
var graphics = game.add.graphics(0, 0);
graphics.lineStyle(6, 0x909090, 0.3);
graphics.drawCircle(dragX, dragY, 200);
}
That works fine, but now I need to remove the circle when the drag ends, and I can't figure that part out:
function dragStop() {
// ?
}
Is it possible to remove graphics? Is there a better or simpler option to draw a circle and remove it later?
You could kill() the object
But be carefull with the scope of the var you want kill (you are defining it inside the function).
Or you could just create the graphic and then show or hide depending of your event (drag)
I leave you a very simple example with both solutions:
var game = new Phaser.Game(500, 500, Phaser.AUTO, 'game');
var mainState = {
create:function(){
var graphics = game.add.graphics(0, 0);
graphics.lineStyle(6, 0x909090, 0.3);
graphics.drawCircle(game.world.centerX+100,game.world.centerY+100, 200);
console.log(graphics);
setTimeout(function(){
graphics.kill();
},2000);
this.graphics2 = game.add.graphics(0, 0);
this.graphics2.lineStyle(6, 0xff0000, 1);
this.graphics2.drawCircle(game.world.centerX-100,game.world.centerY-100, 200);
this.graphics2.visible = false;
this.show_later = game.time.now + 2000;
this.hide_again = game.time.now + 4000;
},
update:function(){
if(this.show_later < game.time.now){
this.graphics2.visible = false;
}
if(this.hide_again < game.time.now){
this.graphics2.visible = true;
}
},
};
game.state.add('main', mainState);
game.state.start('main');
<script src="https://cdnjs.cloudflare.com/ajax/libs/phaser/2.4.4/phaser.min.js"></script>
<div id="game"></div>