Related
I am trying to make an animation system for my three.js project. I have a json file for the information. I was able to make the animation play. But, at the moment they need to be somewhere, they move to that location, instead of slowly moving to that location over time. The json file tells the program where the specific object needs to be at a certain location. For example:
Json File:
{
"right": {
"position": {
"0.0": [0, 0, 0],
"0.25": [0, 1, 0],
"0.5": [1, 1, 0]
}
}
Json files tells you position, at what second, then the positions.
Code:
for (const [key, value] of Object.entries(json.position.right))
if(seconds === json.position.right[key]) {
obj.position.x = json.right.position[key][0];
obj.position.y = json.right.position[key][1];
obj.position.z = json.right.position[key][2];
}
}
In the code, I loop through the json file's right cube position (which tells when the position changes happen). If the seconds match, it moves to that position.
How would I be able to get the movement inbetween the keyframes for the object?
Here is the example: https://mixed-polyester-a.glitch.me/
Here is all the code: https://glitch.com/edit/#!/mixed-polyester-a
I used Blockbench to export models as .OBJ files, materials as .MTL files, and animations as .JSON files.
Sorry if it sounds confusing, didn't really know how to explain it. Any help would be highly appreciated.
Three.js has a method called MathUtils.lerp() that takes in a starting position, an ending position, and an interpolation value between [0, 1]. You could use this to tween your object's current position to its target destination on each frame, as demonstrated in the example below:
const container = document.getElementById("container");
const ball = document.getElementById("ball");
// Set current positions
let ballPosX = 0;
let ballPosY = 0;
// Set target positions
let ballTargetX = 0;
let ballTargetY = 0;
// Update target positions on click
function onClick(event) {
ballTargetX = event.layerX;
ballTargetY = event.layerY;
//console.log(event);
}
function update() {
// Interpolate current position towards targets
ballPosX = THREE.MathUtils.lerp(ballPosX, ballTargetX, 0.1);
ballPosY = THREE.MathUtils.lerp(ballPosY, ballTargetY, 0.1);
// Apply current position to our object
ball.style.left = ballPosX + "px";
ball.style.top = ballPosY + "px";
requestAnimationFrame(update);
}
container.addEventListener("click", onClick);
update();
#container {
width: 300px;
height: 300px;
background: #ddd;
}
#ball {
width: 10px;
height: 10px;
position: absolute;
top: 0;
left: 0;
background: #f90;
border-radius: 10px;
margin-top: -5px;
margin-left: -5px;
}
<div id="container">
<div id="ball"></div>
</div>
<script src="https://threejs.org/build/three.js"></script>
Update:
To create a linear timeline with keyframes, I've used gsap.to() with the keyframes parameter to feed all the positions to the timeline. See here and look up "keyframes" for more details. You can see it in action in the code demo below, you'll need to iterate through your JSON to feed that data to GSAP on your own, though. Good luck!
// Set position vector
const ballPos = {x: 0, y: 0};
const positions = {
"0.0": [0, 0],
"0.25": [0, 100],
"0.5": [100, 100],
"0.75": [100, 0],
"1.0": [0, 0],
}
const timeline = gsap.to(ballPos, {keyframes: [
{x: positions["0.0"][0], y: positions["0.0"][1], duration: 0.0},
{x: positions["0.25"][0], y: positions["0.25"][1], duration: 0.25},
{x: positions["0.5"][0], y: positions["0.5"][1], duration: 0.25},
{x: positions["0.75"][0], y: positions["0.75"][1], duration: 0.25},
{x: positions["1.0"][0], y: positions["1.0"][1], duration: 0.25},
]});
const container = document.getElementById("container");
const ball = document.getElementById("ball");
let timelineTime = 0;
function update() {
timelineTime += 0.001;
timelineTime %= 1;
timeline.seek(timelineTime);
// Apply current position to our object
ball.style.left = ballPos.x + "px";
ball.style.top = ballPos.y + "px";
requestAnimationFrame(update);
}
update();
#container {
width: 300px;
height: 300px;
background: #ddd;
}
#ball {
width: 10px;
height: 10px;
position: absolute;
top: 0;
left: 0;
background: #f90;
border-radius: 10px;
margin-top: -5px;
margin-left: -5px;
}
<div id="container">
<div id="ball"></div>
</div>
<script src="https://threejs.org/build/three.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.7.1/gsap.min.js"></script>
I need to be able to highlight features in a bounding box using Mapbox GL as shown in this example. My map also needs to have the ability to change the style layer, e.g. changing the base style from mapbox://styles/mapbox/light-v10 to mapbox://styles/mapbox/satellite-v9. This has been challenging because Mapbox GL does not natively support the concept of "basemaps" like, e.g. leaflet does.
My solution, after much searching (I believe I eventually followed a workaround posted in a github issue) involved:
Using map.on('style.load') instead of map.on('load') as shown in the example (I believe map.on('style.load') is not part of their public API, but I could be wrong) and,
Iterating through an array of sources/layers (see the vectorTileLayers variable in the code below) to add vector tiles layers to the map every time a new style is loaded.
This works brilliantly for me in some ways - I'm able to add an arbitrary number of vector tile sources to the array, and they are all added back to the map if the base style is changed. However, I am unable to add a query feature following the example provided by Mapbox if the sources/layers are added to the map in an array and iterated through as shown below. The bounding feature works, but when it is drawn, and released, I see the error show by running the snippet below.
This is a simplified version my actual problem that will allow others to run and manipulate the code. In reality, I'm adding my own vector tile layers (stored in the vectorTileLayers array, which works without issue. When I try and add another layer with the same source and different styling, as shown in the example, I'm unable to actually query the desired layer. Any help would be greatly appreciated. Just FYI - the toggleable layers buttons are not showing up in this snippet, but it's not critical to solve the problem.
mapboxgl.accessToken = 'pk.eyJ1IjoiamFtZXljc21pdGgiLCJhIjoiY2p4NTRzdTczMDA1dzRhbXBzdmFpZXV6eCJ9.-k7Um-xmYy4xhNDN6kDvpg';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v10',
center: [-98, 38.88],
minZoom: 2,
zoom: 3
});
var vectorTileLayers = [{
source: {
type: 'vector',
url: 'mapbox://mapbox.82pkq93d'
},
layer: {
id: 'counties',
type: 'fill',
source: 'counties',
'source-layer': 'original',
paint: {
'fill-outline-color': 'rgba(0,0,0,0.1)',
'fill-color': 'rgba(0,0,0,0.1)'
},
}
},
{
source: {
type: 'vector',
url: 'mapbox://mapbox.82pkq93d'
},
layer: {
id: 'counties-highlighted',
type: 'fill',
source: 'counties',
'source-layer': 'original',
paint: {
'fill-outline-color': '#484896',
'fill-color': '#6e599f',
'fill-opacity': 0.75
},
filter: ['in', 'FIPS', '']
}
}
]
map.on('style.load', function() {
for (var i = 0; i < vectorTileLayers.length; i++) {
var tileLayer = vectorTileLayers[i];
map.addSource(tileLayer.layer.source, tileLayer.source);
map.addLayer(tileLayer.layer);
}
var layerList = document.getElementById('basemapmenu');
var inputs = layerList.getElementsByTagName('input');
function switchLayer(layer) {
var layerId = layer.target.id;
map.setStyle('mapbox://styles/mapbox/' + layerId);
}
for (var i = 0; i < inputs.length; i++) {
inputs[i].onclick = switchLayer;
}
});
// Disable default box zooming.
map.boxZoom.disable();
// Create a popup, but don't add it to the map yet.
var popup = new mapboxgl.Popup({
closeButton: false
});
var canvas = map.getCanvasContainer();
var start;
var current;
var box;
canvas.addEventListener('mousedown', mouseDown, true);
// Return the xy coordinates of the mouse position
function mousePos(e) {
var rect = canvas.getBoundingClientRect();
return new mapboxgl.Point(
e.clientX - rect.left - canvas.clientLeft,
e.clientY - rect.top - canvas.clientTop
);
}
function mouseDown(e) {
// Continue the rest of the function if the shiftkey is pressed.
if (!(e.shiftKey && e.button === 0)) return;
// Disable default drag zooming when the shift key is held down.
map.dragPan.disable();
// Call functions for the following events
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
document.addEventListener('keydown', onKeyDown);
// Capture the first xy coordinates
start = mousePos(e);
}
function onMouseMove(e) {
// Capture the ongoing xy coordinates
current = mousePos(e);
// Append the box element if it doesnt exist
if (!box) {
box = document.createElement('div');
box.classList.add('boxdraw');
canvas.appendChild(box);
}
var minX = Math.min(start.x, current.x),
maxX = Math.max(start.x, current.x),
minY = Math.min(start.y, current.y),
maxY = Math.max(start.y, current.y);
// Adjust width and xy position of the box element ongoing
var pos = 'translate(' + minX + 'px,' + minY + 'px)';
box.style.transform = pos;
box.style.WebkitTransform = pos;
box.style.width = maxX - minX + 'px';
box.style.height = maxY - minY + 'px';
}
function onMouseUp(e) {
// Capture xy coordinates
finish([start, mousePos(e)]);
}
function onKeyDown(e) {
// If the ESC key is pressed
if (e.keyCode === 27) finish();
}
function finish(bbox) {
// Remove these events now that finish has been called.
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('keydown', onKeyDown);
document.removeEventListener('mouseup', onMouseUp);
if (box) {
box.parentNode.removeChild(box);
box = null;
}
// If bbox exists. use this value as the argument for `queryRenderedFeatures`
if (bbox) {
var features = map.queryRenderedFeatures(bbox, {
layers: ['counties']
});
if (features.length >= 1000) {
return window.alert('Select a smaller number of features');
}
// Run through the selected features and set a filter
// to match features with unique FIPS codes to activate
// the `counties-highlighted` layer.
var filter = features.reduce(
function(memo, feature) {
memo.push(feature.properties.FIPS);
return memo;
},
['in', 'FIPS']
);
map.setFilter('counties-highlighted', filter);
}
map.dragPan.enable();
}
map.on('mousemove', function(e) {
var features = map.queryRenderedFeatures(e.point, {
layers: ['counties-highlighted']
});
// Change the cursor style as a UI indicator.
map.getCanvas().style.cursor = features.length ? 'pointer' : '';
if (!features.length) {
popup.remove();
return;
}
var feature = features[0];
popup
.setLngLat(e.lngLat)
.setText(feature.properties.COUNTY)
.addTo(map);
});
body {
margin: 0;
padding: 0;
}
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
#basemapmenu {
position: absolute;
display: inline-block;
background-color: transparent;
bottom: 0;
left: 0;
margin-left: 10px;
margin-bottom: 40px;
}
.boxdraw {
background: rgba(56, 135, 190, 0.1);
border: 2px solid #3887be;
position: absolute;
top: 0;
left: 0;
width: 0;
height: 0;
}
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css" rel="stylesheet"/>
<script src="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js"></script>
<div id="map"></div>
<div id='basemapmenu'>
<input id='light-v10' class='btn btn-outline-primary' type='button' name='rtoggle' value='Light' checked='checked'>
<input id='dark-v10' class='btn btn-outline-primary' type='button' name='rtoggle' value='Dark'>
<input id='satellite-v9' class='btn btn-outline-primary' type='button' name='rtoggle' value='Satellite'>
</div>
I ran you code locally and found the problem!
You are trying to add same source twice and mapbox was giving you error for that, if you check your Console:
Problem: There can be only one source with one ID on Map
Error: There is already a source with this ID
After first loop execute successfully. On second iteration, it fails to add same source twice, code breaks here and doesn't continue further. Hence doesn't add second layer(therefore no functionality works thereafter!)
Fix:(Added a small check to not add same source if already added)
map.on('style.load', function () {
for (var i = 0; i < vectorTileLayers.length; i++) {
var tileLayer = vectorTileLayers[i];
if (!map.getSource(tileLayer.layer.source,)) //FIX
map.addSource(tileLayer.layer.source, tileLayer.source);
map.addLayer(tileLayer.layer);
}
var layerList = document.getElementById('basemapmenu');
var inputs = layerList.getElementsByTagName('input');
function switchLayer(layer) {
var layerId = layer.target.id;
map.setStyle('mapbox://styles/mapbox/' + layerId);
}
for (var i = 0; i < inputs.length; i++) {
inputs[i].onclick = switchLayer;
}
});
I can see the Code is working fine:
Hope this will fix the problem.
I'll leave the above answer from Dolly as accepted because it answers my original question exactly. Unforunately, it didn't exactly solve my actual problem. After some additional fiddling, I wanted to post a fully functional version for reference in case people come across this looking for a solution to adding toggleable "basemap" layers in Mapbox GL with or without user interactivity options like this one.
The solution above does not work after toggling between basemap layers. The vector tile layers are not reloaded properly. The solution below seems to do the trick. It uses map.on('styledata' ...) rather than map.on('style.load' ...), which seems to allow for loading layers by the more conventional method of first calling map.addSource() followed by map.addLayer(). You can load a single source, and then an arbitrary number of layers pointing to that source. So in my "real world" example, I load 3 sources, and 5 layers from those sources.
(FYI - for some reason the built-in snippet tool from stack overflow isn't rendering the basemap buttons. If you copy the code exactly into JS fiddle or codepen it will work)
I hope this helps future humans - it seems like lots of people have had problems with managing Mapbox styles with custom layers on top. I certainly have.
mapboxgl.accessToken =
"pk.eyJ1IjoiamFtZXljc21pdGgiLCJhIjoiY2p4NTRzdTczMDA1dzRhbXBzdmFpZXV6eCJ9.-k7Um-xmYy4xhNDN6kDvpg";
var map = new mapboxgl.Map({
container: "map",
style: "mapbox://styles/mapbox/light-v10",
center: [-98, 38.88],
minZoom: 2,
zoom: 3
});
map.on("styledata", function() {
map.addSource("counties", {
type: "vector",
url: "mapbox://mapbox.82pkq93d"
});
map.addLayer({
id: "counties",
type: "fill",
source: "counties",
"source-layer": "original",
paint: {
"fill-outline-color": "rgba(0,0,0,0.1)",
"fill-color": "rgba(0,0,0,0.1)"
}
});
map.addLayer({
id: "counties-highlighted",
type: "fill",
source: "counties",
"source-layer": "original",
paint: {
"fill-outline-color": "#484896",
"fill-color": "#6e599f",
"fill-opacity": 0.75
},
filter: ["in", "FIPS", ""]
});
var layerList = document.getElementById("basemapmenu");
var inputs = layerList.getElementsByTagName("input");
function switchLayer(layer) {
var layerId = layer.target.id;
map.setStyle("mapbox://styles/mapbox/" + layerId);
}
for (var i = 0; i < inputs.length; i++) {
inputs[i].onclick = switchLayer;
}
});
// Disable default box zooming.
map.boxZoom.disable();
// Create a popup, but don't add it to the map yet.
var popup = new mapboxgl.Popup({
closeButton: false
});
var canvas = map.getCanvasContainer();
var start;
var current;
var box;
canvas.addEventListener("mousedown", mouseDown, true);
// Return the xy coordinates of the mouse position
function mousePos(e) {
var rect = canvas.getBoundingClientRect();
return new mapboxgl.Point(
e.clientX - rect.left - canvas.clientLeft,
e.clientY - rect.top - canvas.clientTop
);
}
function mouseDown(e) {
// Continue the rest of the function if the shiftkey is pressed.
if (!(e.shiftKey && e.button === 0)) return;
// Disable default drag zooming when the shift key is held down.
map.dragPan.disable();
// Call functions for the following events
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
document.addEventListener("keydown", onKeyDown);
// Capture the first xy coordinates
start = mousePos(e);
}
function onMouseMove(e) {
// Capture the ongoing xy coordinates
current = mousePos(e);
// Append the box element if it doesnt exist
if (!box) {
box = document.createElement("div");
box.classList.add("boxdraw");
canvas.appendChild(box);
}
var minX = Math.min(start.x, current.x),
maxX = Math.max(start.x, current.x),
minY = Math.min(start.y, current.y),
maxY = Math.max(start.y, current.y);
// Adjust width and xy position of the box element ongoing
var pos = "translate(" + minX + "px," + minY + "px)";
box.style.transform = pos;
box.style.WebkitTransform = pos;
box.style.width = maxX - minX + "px";
box.style.height = maxY - minY + "px";
}
function onMouseUp(e) {
// Capture xy coordinates
finish([start, mousePos(e)]);
}
function onKeyDown(e) {
// If the ESC key is pressed
if (e.keyCode === 27) finish();
}
function finish(bbox) {
// Remove these events now that finish has been called.
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("keydown", onKeyDown);
document.removeEventListener("mouseup", onMouseUp);
if (box) {
box.parentNode.removeChild(box);
box = null;
}
// If bbox exists. use this value as the argument for `queryRenderedFeatures`
if (bbox) {
var features = map.queryRenderedFeatures(bbox, {
layers: ["counties"]
});
if (features.length >= 1000) {
return window.alert("Select a smaller number of features");
}
// Run through the selected features and set a filter
// to match features with unique FIPS codes to activate
// the `counties-highlighted` layer.
var filter = features.reduce(
function(memo, feature) {
memo.push(feature.properties.FIPS);
return memo;
}, ["in", "FIPS"]
);
map.setFilter("counties-highlighted", filter);
}
map.dragPan.enable();
}
map.on("mousemove", function(e) {
var features = map.queryRenderedFeatures(e.point, {
layers: ["counties-highlighted"]
});
// Change the cursor style as a UI indicator.
map.getCanvas().style.cursor = features.length ? "pointer" : "";
if (!features.length) {
popup.remove();
return;
}
var feature = features[0];
popup.setLngLat(e.lngLat).setText(feature.properties.COUNTY).addTo(map);
});
body {
margin: 0;
padding: 0;
}
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
#basemapmenu {
position: absolute;
display: inline-block;
background-color: transparent;
bottom: 0;
left: 0;
margin-left: 10px;
margin-bottom: 40px;
}
.boxdraw {
background: rgba(56, 135, 190, 0.1);
border: 2px solid #3887be;
position: absolute;
top: 0;
left: 0;
width: 0;
height: 0;
}
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css" rel="stylesheet" />
<script src="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js"></script>
<div id="map"></div>
<div id='basemapmenu'>
<input id='light-v10' class='btn btn-outline-primary' type='button' name='rtoggle' value='Light' checked='checked'>
<input id='dark-v10' class='btn btn-outline-primary' type='button' name='rtoggle' value='Dark'>
<input id='satellite-v9' class='btn btn-outline-primary' type='button' name='rtoggle' value='Satellite'>
</div>
How can I get Text object font size after modifying object in fabric.js?
Below is my code.
var text = new fabric.Text(imgText, {
left: 10,
top: 5,
fontSize: 15,
fontFamily: 'Verdana',
fill: 'white'
});
text.scaleToWidth(canvas.width * 0.5);
text.setControlsVisibility(canvasConfig);
canvas.add(text);
canvas.renderAll();
var objects = canvas.getActiveObject();
var obj = objects;
if (!obj) {
return;
}
//console.log(obj.get('fontSize') *= obj.scaleX);
var angle = obj.get('angle');
var objWidth = obj.get('width') * obj.scaleX;
var objWidthPercent = objWidth / canvas.width * 100;
var objHeight = obj.get('height') * obj.scaleY;
var objHeightPercent = objHeight / canvas.height * 100;
var bound = obj.getBoundingRect();
var objLeft = obj.get('left') / canvas.width * 100;
var objTop = obj.get('top') / canvas.height * 100;
var newfontsize = obj.fontSize * obj.scaleX;
Above I set default FontSize to 15. then I modify object I can get proper Height, Width, Left, Top, but I am not able to get FontSize.
In backend i set image and text like below screenshot.
In frontend what i get like below screenshot.
Below style for image & text on frontend.
element.style {
left: 64.37%;
top: 14.54%;
width: 28.25%;
height: 14.37%;
font-size: 63.58px;
color: #E346FF;
font-family: Times New Roman;
position: absolute;
max-width: 100%;
z-index: 996;
max-height: 100%;
}
element.style {
left: 56.5%;
top: 0.81%;
width: 42.86%;
height: 42.86%;
max-width: 100%;
max-height: 100%;
position: absolute;
z-index: 995;
display: block;
background-image: url(http://10.16.16.101/LabelExtension/pub/media/labelimageuploader/images/image/o/r/orange_38.png);
background-position: center;
background-repeat: no-repeat;
background-size: contain;
}
When i add below code it working perfect but in backend object is getting blur.
this.canvas.setHeight(300);
this.canvas.setWidth(240);
this.canvas.backgroundColor = '#E8EEF1';
this.canvas.setDimensions({width: '480px', height: '600px'}, {cssOnly: true});
Here is the way to match Fabric.js font transforms to CSS transforms: https://jsfiddle.net/mmalex/evpky3tn/
The solution is to match transformations of texts, not trying to adjust font size.
Step 1 - Prepare scene, compose canvas, group text with rectangle, let user manipulate this group, rotate and scale it.
var canvas = new fabric.Canvas(document.getElementById('c'));
var rect = new fabric.Rect({
width: 50,
height: 50,
left: 90,
top: 120,
fill: 'rgba(255,0,0,0.25)'
});
var text = new fabric.Text("123", {
left: rect.left,
top: rect.top,
fontSize: 15,
fontFamily: 'Verdana',
fill: 'black'
});
text.scaleToWidth(rect.width);
canvas.add(text);
var group = new fabric.Group([rect, text], {
originX: 'center',
originY: 'center',
angle: 25,
scaleY: 1.7
});
canvas.add(group);
canvas.renderAll();
Step 2 - prepare DIV style for scaling
.scaled { // this style is applied to DIV element with text
...
font-size: 15px; // Fabric.js text is also 15px size
transform: scale(1, 1); // define initial transform
transition: all 0.25s linear; // let it animate
...
}
Step 3 - Evaluate Fabric.js transforms and apply CSS on DIV element,
for example:
element.style {
transform: rotate(25deg) scale(1.74774, 2.97116);
}
The solution (aka button handler) converts Fabric.js transform to CSS transform:
function matchCssTransform() {
let textScale = text.getTotalObjectScaling();
let pixelRatio = window.devicePixelRatio;
let styleStr = `rotate(${group.angle}deg) scale(${textScale.scaleX / pixelRatio}, ${textScale.scaleY / pixelRatio}) `;
document.getElementById("scaled").style.transform = styleStr;
}
getHeightOfLine(lineIndex) → {Number} : Computes height of character at given position and return fontSize of the character. Is it? tell me.
It's a method of text class. http://fabricjs.com/docs/fabric.Text.html#getHeightOfLine
This is the vanilla js solution.
I am not able to get FontSize.
You can achieve fontSize using the computedStyle like
let style = window.getComputedStyle(text, null).getPropertyValue('font-size');
let fontSize = parseFloat(style);
https://developer.mozilla.org/de/docs/Web/API/Window/getComputedStyle
In addition to this I recommend you to work with vw and vh
.text {
font-size: 10vw;
}
https://web-design-weekly.com/2014/11/18/viewport-units-vw-vh-vmin-vmax/
All together gives us https://jsfiddle.net/a8woL1eh/
The problem is Text object does not change its fontSize property when it is modified or transformed unless set fontSize property manually. It's the reason why you get blurred text because despite changed the transform size the main fontSize remaining same. So possible solution will be somehow changing fontSize property dynamically.
http://jsfiddle.net/therowf/xkhwagd8/41/
Here I have attached a jsfiddle example form your code. This code is very similar to your one.
Thanks and let me know if this is worked. (:
var canvas = new fabric.Canvas(document.getElementById('c'));
var imgText ="This is text";
var canvasConfig = true;
var text = new fabric.Text(imgText, {
left: 10,
top: 5,
fontSize: 50,
fontFamily: 'Verdana',
fill: 'black'
});
text.scaleToWidth(canvas.width * 0.5);
text.setControlsVisibility(canvasConfig);
canvas.add(text);
canvas.renderAll();
var objects = canvas.getActiveObject();
function moveHandler(evt){
//evt.target.fontSize = 10;
var fontSizeX = evt.target.scaleX;
var fontSizeY = evt.target.scaleY;
if(fontSizeX === fontSizeY){
evt.target.fontSize = fontSizeX*100;
console.log(evt.target.fontSize);
}
}
canvas.on('object:scaling', moveHandler);
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.6.0/fabric.min.js"></script>
<canvas id="c" width="400" height="400"></canvas>
The Problem
I want to do this with an image instead of a canvas object. Meaning you have to add the thing you want to add TO AND AS A PART of the canvas before you can add it. The images are actually part of the website so it doesn't need to do some intricate stuff. This code I found here only works for when it's an object not an actual element. And by the way I'm using FabricJS just to let you know that I'm not using the default HTML5 canvas stuff.
As for any alternatives that are possibly going to work without using my current code. Please do post it down below in the comments or in the answers. I would really love to see what you guys got in mind.
Summary
Basically I want to be able to drag and drop images through the canvas while retaining the mouse cursor position. For example if I drag and image and the cursor was at x: 50 y: 75 it would drop the image to that exact spot. Just like what the code I found does. But like the problem stated the CODE uses an object for you to drag it to the canvas then it clones it. I want this functionality using just plain old elements. E.g: <img>.
THE CODE -
JsFiddle
window.canvas = new fabric.Canvas('fabriccanvas');
var edgedetection = 8; //pixels to snap
canvas.selection = false;
window.addEventListener('resize', resizeCanvas, false);
function resizeCanvas() {
canvas.setHeight(window.innerHeight);
canvas.setWidth(window.innerWidth);
canvas.renderAll();
}
// resize on init
resizeCanvas();
//Initialize Everything
init();
function init(top, left, width, height, fill) {
var bg = new fabric.Rect({
left: 0,
top: 0,
fill: "#eee",
width: window.innerWidth,
height: 75,
lockRotation: true,
maxHeight: document.getElementById("fabriccanvas").height,
maxWidth: document.getElementById("fabriccanvas").width,
selectable: false,
});
var squareBtn = new fabric.Rect({
top: 10,
left: 18,
width: 40,
height: 40,
fill: '#af3',
lockRotation: true,
originX: 'left',
originY: 'top',
cornerSize: 15,
hasRotatingPoint: false,
perPixelTargetFind: true,
});
var circleBtn = new fabric.Circle({
radius: 20,
fill: '#f55',
top: 10,
left: 105,
});
var triangleBtn = new fabric.Triangle({
width: 40,
height: 35,
fill: 'blue',
top: 15,
left: 190,
});
var sqrText = new fabric.IText("Add Square", {
fontFamily: 'Indie Flower',
fontSize: 14,
fontWeight: 'bold',
left: 6,
top: 50,
selectable: false,
});
var cirText = new fabric.IText("Add Circle", {
fontFamily: 'Indie Flower',
fontSize: 14,
fontWeight: 'bold',
left: 95,
top: 50,
selectable: false,
});
var triText = new fabric.IText("Add Triangle", {
fontFamily: 'Indie Flower',
fontSize: 14,
fontWeight: 'bold',
left: 175,
top: 50,
selectable: false,
});
var shadow = {
color: 'rgba(0,0,0,0.6)',
blur: 3,
offsetX: 0,
offsetY: 2,
opacity: 0.6,
fillShadow: true,
strokeShadow: true
};
window.canvas.add(bg);
bg.setShadow(shadow);
window.canvas.add(squareBtn);
window.canvas.add(circleBtn);
window.canvas.add(triangleBtn);
window.canvas.add(sqrText);
window.canvas.add(cirText);
window.canvas.add(triText);
canvas.forEachObject(function (e) {
e.hasControls = e.hasBorders = false; //remove borders/controls
});
function draggable(object) {
object.on('mousedown', function() {
var temp = this.clone();
temp.set({
hasControls: false,
hasBorders: false,
});
canvas.add(temp);
draggable(temp);
});
object.on('mouseup', function() {
// Remove an event handler
this.off('mousedown');
// Comment this will let the clone object able to be removed by drag it to menu bar
// this.off('mouseup');
// Remove the object if its position is in menu bar
if(this.top<=75) {
canvas.remove(this);
}
});
}
draggable(squareBtn);
draggable(circleBtn);
draggable(triangleBtn);
this.canvas.on('object:moving', function (e) {
var obj = e.target;
obj.setCoords(); //Sets corner position coordinates based on current angle, width and height
canvas.forEachObject(function (targ) {
activeObject = canvas.getActiveObject();
if (targ === activeObject) return;
if (Math.abs(activeObject.oCoords.tr.x - targ.oCoords.tl.x) < edgedetection) {
activeObject.left = targ.left - activeObject.currentWidth;
}
if (Math.abs(activeObject.oCoords.tl.x - targ.oCoords.tr.x) < edgedetection) {
activeObject.left = targ.left + targ.currentWidth;
}
if (Math.abs(activeObject.oCoords.br.y - targ.oCoords.tr.y) < edgedetection) {
activeObject.top = targ.top - activeObject.currentHeight;
}
if (Math.abs(targ.oCoords.br.y - activeObject.oCoords.tr.y) < edgedetection) {
activeObject.top = targ.top + targ.currentHeight;
}
if (activeObject.intersectsWithObject(targ) && targ.intersectsWithObject(activeObject)) {
} else {
targ.strokeWidth = 0;
targ.stroke = false;
}
if (!activeObject.intersectsWithObject(targ)) {
activeObject.strokeWidth = 0;
activeObject.stroke = false;
activeObject === targ
}
});
});
}
More codes that I found but doesn't answer my problem:
var canvas = new fabric.Canvas('c');
canvas.on("after:render", function(){canvas.calcOffset();});
var started = false;
var x = 0;
var y = 0;
var width = 0;
var height = 0;
canvas.on('mouse:down', function(options) {
//console.log(options.e.clientX, options.e.clientY);
x = options.e.clientX;
y = options.e.clientY;
canvas.on('mouse:up', function(options) {
width = options.e.clientX - x;
height = options.e.clientY - y;
var square = new fabric.Rect({
width: width,
height: height,
left: x + width/2 - canvas._offset.left,
top: y + height/2 - canvas._offset.top,
fill: 'red',
opacity: .2
});
canvas.add(square);
canvas.off('mouse:up');
$('#list').append('<p>Test</p>');
});
});
This code adds a rectangle to the canvas. But the problem is this doesn't achieve what I want which is basically, as previously stated, that I want to be able to drag an img element and then wherever you drag that image on the canvas it will drop it precisely at that location.
CREDITS
Fabric JS: Copy/paste object on mouse down
fabric.js Offset Solution
it doesn't need to do some intricate stuff.
A logical framework under which to implement image dragging might be to monitor mouse events using event listeners.
On mouse down over an Image element within the page but not over the canvas, record which image element and the mouse position within the image. On mouse up anywhere set this record back to null.
On mouse over of the canvas with a non empty image record, create a Fabric image element from the Image element (as per documentation), calculate where it goes from the recorded image position and current mouse position, paste it under the mouse, make it draggable and simulate the effect of a mouse click to continue dragging it.
I have taken this question to be about the design and feasibility stages of program development.
Hello I was trying to find a solution for a small script that can rotate objects around 1 center but it seems a bit too tricky for me.
I've found almost perfect solution and tried to modify it to suit my needs but there's a problem.
I'm trying to make 3 objects with text to rotate with same speed, same orbit but different start positions as if they were apexes(vertices) of equilateral triangle.
Here's my fiddle so far:
( function ( $ ) {
jQuery.fn.orbit = function(s, options){
var settings = {
orbits: 1 // Number of times to go round the orbit e.g. 0.5 = half an orbit
,period: 3000 // Number of milliseconds to complete one orbit.
,maxfps: 25 // Maximum number of frames per second. Too small gives "flicker", too large uses lots of CPU power
,clockwise: false // Direction of rotation.
};
$.extend(settings, options); // Merge the supplied options with the default settings.
return(this.each(function(){
var p = $(this);
/* First obtain the respective positions */
var p_top = p.css('top' ),
p_left = p.css('left'),
s_top = s.css('top' ),
s_left = s.css('left');
/* Then get the positions of the centres of the objects */
var p_x = parseInt(p_top ) + p.height()/2,
p_y = parseInt(p_left) + p.width ()/2,
s_x = parseInt(s_top ) + s.height()/2,
s_y = parseInt(s_left) + s.width ()/2;
/* Find the Adjacent and Opposite sides of the right-angled triangle */
var a = s_x - p_x,
o = s_y - p_y;
/* Calculate the hypotenuse (radius) and the angle separating the objects */
var r = Math.sqrt(a*a + o*o);
var theta = Math.acos(a / r);
/* Calculate the number of iterations to call setTimeout(), the delay and the "delta" angle to add/subtract */
var niters = Math.ceil(Math.min(4 * r, settings.period, 0.001 * settings.period * settings.maxfps));
var delta = 2*Math.PI / niters;
var delay = settings.period / niters;
if (! settings.clockwise) {delta = -delta;}
niters *= settings.orbits;
/* create the "timeout_loop function to do the work */
var timeout_loop = function(s, r, theta, delta, iter, niters, delay, settings){
setTimeout(function(){
/* Calculate the new position for the orbiting element */
var w = theta + iter * delta;
var a = r * Math.cos(w);
var o = r * Math.sin(w);
var x = parseInt(s.css('left')) + (s.height()/2) - a;
var y = parseInt(s.css('top' )) + (s.width ()/2) - o;
/* Set the CSS properties "top" and "left" to move the object to its new position */
p.css({top: (y - p.height()/2),
left: (x - p.width ()/2)});
/* Call the timeout_loop function if we have not yet done all the iterations */
if (iter < (niters - 1)) timeout_loop(s, r, theta, delta, iter+1, niters, delay, settings);
}, delay);
};
/* Call the timeout_loop function */
timeout_loop(s, r, theta, delta, 0, niters, delay, settings);
}));
}
}) (jQuery);
$('#object1' ).orbit($('#center' ), {orbits: 2, period: 8000});
$('#object2' ).orbit($('#center' ), {orbits: 4, period: 4000});
$('#object3' ).orbit($('#center' ), {orbits: 8, period: 2000});
HTML:
<h1> Example</h1>
<div id='rotation'>
<div id='center' >C</div>
<div id='object1' >Text1</div>
<div id='object2' >Text2</div>
<div id='object3' >Text3</div>
</div>
CSS:
#rotation {position: relative; width: 600px; height: 600px; background-color: #898989}
#center {position: absolute; width: 20px; height: 20px;
top: 300px; left: 300px; background-color: #ffffff;
-moz-border-radius: 40px; border-radius: 40px;
text-align: center; line-height: 15px;
}
#object1 {position: absolute; width: 36px; height: 36px;
top: 300px; left: 200px; background-color: #ff8f23;
-moz-border-radius: 18px; border-radius: 18px;
text-align: center; line-height: 30px;
}
#object2 {position: absolute; width: 36px; height: 36px;
top: 300px; left: 200px; background-color: #ff8f23;
-moz-border-radius: 18px; border-radius: 18px;
text-align: center; line-height: 30px;
}
#object3 {position: absolute; width: 36px; height: 36px;
top: 300px; left: 200px; background-color: #ff8f23;
-moz-border-radius: 18px; border-radius: 18px;
text-align: center; line-height: 30px;
}
I used different speed and revolutions for each object because I can't figure out how to set different start positions without messing up. If I touch x,y coordinates of any object in CSS then the orbiting messes up. It seems that object positions calculations are connected. And if I change coordinates then the calculation also changes. But I can't figure out how to fix this.
If you change the starting position of each element and call the .orbit function on each of them passing the same arguments it should work.
Check this out: fiddle
This is a slightly modified version of your fiddle. (Changed the starting positions and the arguments when calling the .orbit function.
Correct me if I'm wrong or if I didn't answer your question.
Got it working as intended
Fiddle
Jquery code:
var txt = $('#text .txt'), txtLen = txt.size();
var deg2rad = function(a) { return a*Math.PI/180.0; }
var angle = 0, speed=0.1, delay = 0, r = 100;
(function rotate() {
for (var i=0; i<txtLen; i++) {
var a = angle - (i * 360 / txtLen);
$(txt[i]).css({top: r+(Math.sin(deg2rad(a))*r), left: r+(Math.cos(deg2rad(a))*r)});
}
angle = (angle - speed) % 360;
setTimeout(rotate, delay);
})();
var rotation = function (){
$("#image").rotate({
duration: 6000,
angle:360,
animateTo:0,
callback: rotation,
easing: function (x,t,b,c,d){
var d = 6000
return c*(t/d)+b;
}
});
}
rotation();
var txt2 = $('#text2 .txt2'), txt2Len = txt2.size();
var deg2rad = function(a) { return a*Math.PI/180.0; }
var angle = 13, speed=0.2, delay = 0, r = 100;
(function rotate() {
for (var i=0; i<txt2Len; i++) {
var a = angle - (i * 360 / txt2Len);
$(txt2[i]).css({top: r+(Math.sin(deg2rad(a))*r), left:
r+(Math.cos(deg2rad(a))*r)});
}
// increment our angle and use a modulo so we are always in the range [0..360] degrees
angle = (angle - speed) % 360;
// after a slight delay, call the exact same function again
setTimeout(rotate, delay);
})(); // invoke this boxed function to initiate the rotation
var rotation = function (){
$("#image").rotate({
duration: 6000,
angle:360,
animateTo:0,
callback: rotation,
easing: function (x,t,b,c,d){
var d = 6000
return c*(t/d)+b;
}
});
}
rotation();