I've subclassed the Image class so that I can add a label to it. However, the text seems to get rendered before the image such that the image is hiding the label. Below is the class declaration. Note that I tried calling callSuper() at the end of the _render() method, which didn't make sense to me anyway, and it didn't work. Seems to me if super is called first and then text is added after it should be on top right?
var Cabinet = fabric.util.createClass(fabric.Image, {
type: 'cabinet',
initialize: function(element, options) {
options || (options = { });
this.callSuper('initialize', element, options);
this.set('label', options.label || '');
this.set('height', options.height || 48);
this.set('width', options.width || 24);
},
toObject: function() {
return fabric.util.object.extend(this.callSuper('toObject'), {
label: this.get('label')
});
},
_render: function(ctx) {
this.callSuper('_render', ctx);
var txtOffset = this.label.length * 6;
ctx.fillStyle = '#000';
if (this.get('angle') !== 0) {
ctx.font = '11px Arial Narrow';
//ctx.save();
ctx.rotate(90 * Math.PI/180);
ctx.fillText(this.label, (this.width / 2) - (txtOffset / 2) - 8, this.height - 10);
//ctx.restore();
} else {
ctx.font = "9px Arial Narrow";
ctx.fillText(this.label, (this.width / 2) - (txtOffset / 2) - 8, this.height - 10);
}
}
});
I would appreciate any help you can provide.
Thanks,
Rob
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);
}
});
Looking to change the color of my tooltip but not having much success. Is this a simple need for a color onto the dojo.style command? Here is my code so far
// create node for the tooltip
var tip = "Click on problem location";
var tooltip = dojo.create("div", { "class": "tooltip", "innerHTML": tip }, map.container);
dojo.style(tooltip, "position", "fixed");
// update the tooltip as the mouse moves over the map
dojo.connect(map, "onMouseMove", function(evt) {
var px, py;
if (evt.clientX || evt.pageY) {
px = evt.clientX;
py = evt.clientY;
} else {
px = evt.clientX + dojo.body().scrollLeft - dojo.body().clientLeft;
py = evt.clientY + dojo.body().scrollTop - dojo.body().clientTop;
}
// dojo.style(tooltip, "display", "none");
tooltip.style.display = "none";
dojo.style(tooltip, { left: (px + 15) + "px", top: (py) + "px" });
// dojo.style(tooltip, "display", "");
tooltip.style.display = "";
// console.log("updated tooltip pos.");
});
// hide the tooltip the cursor isn't over the map
dojo.connect(map, "onMouseOut", function(evt){
tooltip.style.display = "none";
});
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);
});
})
I'm using animate method to animate 100 objects, but in my case, the performance is so slow, how do I fix it?
my demo code:
https://jsfiddle.net/cs6jqj2w/
Please take a look at fabricJS demo
Also, I modified little bit that demo using your function for generating random numbers and created 100 shpaes in this fiddle
(function() {
var canvas = this.__canvas = new fabric.Canvas('c');
fabric.Object.prototype.transparentCorners = false;
var Cross = fabric.util.createClass(fabric.Object, {
objectCaching: false,
initialize: function(options) {
this.callSuper('initialize', options);
this.animDirection = 'up';
this.width = 100;
this.height = 100;
this.w1 = this.h2 = 100;
this.h1 = this.w2 = 30;
},
animateWidthHeight: function() {
var interval = 2;
if (this.h2 >= 30 && this.h2 <= 100) {
var actualInterval = (this.animDirection === 'up' ? interval : -interval);
this.h2 += actualInterval;
this.w1 += actualInterval;
}
if (this.h2 >= 100) {
this.animDirection = 'down';
this.h2 -= interval;
this.w1 -= interval;
}
if (this.h2 <= 30) {
this.animDirection = 'up';
this.h2 += interval;
this.w1 += interval;
}
},
_render: function(ctx) {
ctx.fillRect(-this.w1 / 2, -this.h1 / 2, this.w1, this.h1);
ctx.fillRect(-this.w2 / 2, -this.h2 / 2, this.w2, this.h2);
}
});
for (var i = 0; i < 100; i++){
canvas.add(
new Cross({ top: getRandomInt(0,500), left: getRandomInt(0,500)})
);
}
setTimeout(function animate() {
canvas.forEachObject(function(obj){ obj.animateWidthHeight(); obj.dirty = true; });
canvas.renderAll();
setTimeout(animate, 10);
}, 10);
})();
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
UPDATE:
Your animation didn't work because you had to much rendering of the canvas.
You have to generate 99 items without rendering and last one with the rendering. Also, last item has to me with the maximum duration for the animation in order to complete animation for all shapes.
var fabric = window.fabric
var canvas = new fabric.StaticCanvas('c')
function createItem(canvas) {
var item = new fabric.Circle({
left: -100,
top: getRandomInt(0, 500),
opacity: Math.random().toFixed(2),
radius: getRandomInt(10, 50),
})
item.keepGoing = true
canvas.add(item)
// itemTopAnim(canvas, item, getNextTop(item.top))
// itemLeftAnim(canvas, item)
return item;
}
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function getNextTop(top) {
if (top < (canvas.height / 2)) {
return top + getRandomInt(50, 200)
}
return top - getRandomInt(50, 200)
}
function itemTopAnim(canvas, item, top) {
item.animate('top', top, {
duration: getRandomInt(1, 3) * 1000,
// onChange: canvas.renderAll.bind(canvas),
easing: fabric.util.ease.easeInOutCubic,
onComplete: function() {
item.keepGoing && itemTopAnim(canvas, item, getNextTop(item.top))
}
})
}
function itemTopAnimLast(canvas, item, top) {
item.animate('top', top, {
duration: 3 * 1000,
onChange: canvas.renderAll.bind(canvas),
easing: fabric.util.ease.easeInOutCubic,
onComplete: function() {
item.keepGoing && itemTopAnim(canvas, item, getNextTop(item.top))
}
})
}
function itemLeftAnim(canvas, item) {
item.animate('left', canvas.width - item.radius, {
duration: getRandomInt(5, 10) * 1000,
//onChange: canvas.renderAll.bind(canvas),
onComplete: function() {
item.keepGoing = false
}
})
}
function itemLeftAnimLast(canvas, item) {
item.animate('left', canvas.width - item.radius, {
duration: 10 * 1000,
onChange: canvas.renderAll.bind(canvas),
onComplete: function() {
item.keepGoing = true
}
})
}
for (var i = 0; i < 100; i++) {
var item = createItem(canvas);
if (i == 99){
itemLeftAnimLast(canvas, item)
itemTopAnimLast(canvas, item, getNextTop(item.top))
} else {
itemLeftAnim(canvas, item)
itemTopAnim(canvas, item, getNextTop(item.top))
}
}
Check this updated fiddle
Hopefully, it gives you more idea how it works right now.
Sounds like you've got it working now. Just thought I would add that the docs on http://fabricjs.com/fabric-intro-part-2 also mention that you might want to use requestAnimationFrame instead of the onChange callback when animating lots of objects:
The reason animate doesn't automatically re-render canvas after each change is due to performance. After all, we can have hundreds or thousands animating objects on canvas, and it wouldn't be good if every one of them tried to re-render screen. In the case of many objects, you could use something like requestAnimationFrame (or other timer-based) loop to render canvas continuosly on its own, without calling renderAll for each object. But most of the time, you'll probably need to explicitly specify canvas.renderAll as "onChange" callback.
I had similar problems to yours, and this advice worked quite well.
I'm trying to use SVGRenderer in three.js (http://threejs.org/examples/#svg_sandbox). The example shows you how to make an SVG element (a circle) on the fly. I want to import an SVG file that I already have in my computer. How would I do that?
The createElementNS command doesn't seem to support importing SVG files?
I essentially want my image.svg to be displayed on a three.js scene.
You can use the THREE.SVGLoader() Library to achieve it :
var svgManager = new THREE.LegacySVGLoader();
var url = 'https://upload.wikimedia.org/wikipedia/commons/b/b0/NewTux.svg';
function svg_loading_done_callback(doc) {
init(new THREE.SVGObject(doc));
animate();
};
svgManager.load(url,
svg_loading_done_callback,
function() {
console.log("Loading SVG...");
},
function() {
console.log("Error loading SVG!");
});
var camera, scene, renderer;
function init(svgObject) {
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 100000);
camera.position.z = 1500;
svgObject.position.x = Math.random() * innerWidth;
svgObject.position.y = 200;
svgObject.position.z = Math.random() * 10000 - 5000;
svgObject.scale.x = svgObject.scale.y = svgObject.scale.z = 0.01;
scene = new THREE.Scene();
scene.add(svgObject);
var ambient = new THREE.AmbientLight(0x80ffff);
scene.add(ambient);
var directional = new THREE.DirectionalLight(0xffff00);
directional.position.set(-1, 0.5, 0);
scene.add(directional);
renderer = new THREE.SVGRenderer();
renderer.setClearColor(0xf0f0f0);
renderer.setSize(window.innerWidth, window.innerHeight - 5);
document.body.appendChild(renderer.domElement);
window.addEventListener('resize', onWindowResize, false);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
var time = Date.now() * 0.0002;
camera.position.x = Math.sin(time) * 200;
camera.position.z = Math.cos(time) * 200;
camera.lookAt(scene.position);
renderer.render(scene, camera);
}
* {
margin: 0
}
<script src="http://threejs.org/build/three.min.js"></script>
<script src="http://threejs.org/examples/js/renderers/SVGRenderer.js"></script>
<script src="http://threejs.org/examples/js/renderers/Projector.js"></script>
<script src="http://threejs.org/examples/js/loaders/SVGLoader.js"></script>
<script>
/**
* #name LegacySVGLoader
* #author mrdoob / http://mrdoob.com/
* #author zz85 / http://joshuakoo.com/
* #see https://github.com/mrdoob/three.js/issues/14387
*/
THREE.LegacySVGLoader = function(manager) {
this.manager = (manager !== undefined) ? manager : THREE.DefaultLoadingManager;
};
THREE.LegacySVGLoader.prototype = {
constructor: THREE.LegacySVGLoader,
load: function(url, onLoad, onProgress, onError) {
var scope = this;
var parser = new DOMParser();
var loader = new THREE.FileLoader(scope.manager);
loader.load(url, function(svgString) {
var doc = parser.parseFromString(svgString, 'image/svg+xml'); // application/xml
onLoad(doc.documentElement);
}, onProgress, onError);
}
};
</script>
This is current answer from latest three.js lib:
https://threejs.org/examples/?q=svg#webgl_loader_svg
but this code loads svg into geometry mesh, so it is not a DOM element, it zooms out just like any other 3d object. Its like a billboard in 3d scene. If someone needs this kind of behavior then its fine.
If you want to load SVG to be DOM element, to have it float over 3D scene, to be able to add mouse click and hover events... I asked for example and here it is:
https://github.com/mrdoob/three.js/issues/15528