Related
I was trying to implement the FLIP animation, to see if I understood it properly.
In this codepen (pardon the awful code, I was just hacking around), if I comment out the sleep, the smooth transition no longer works. The div changes position abruptly. This is strange because the sleep is for 0ms.
import React, { useRef, useState } from "https://esm.sh/react#18";
import ReactDOM from "https://esm.sh/react-dom#18";
let first = {}
let second = {}
const sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const App = () => {
const [start, setStart] = useState(true);
const boxRefCb = async el => {
if (!el) return;
el.style.transition = "";
const x = parseInt(el?.getBoundingClientRect().x, 10);
const y = parseInt(el?.getBoundingClientRect().y, 10);
first = { x: second.x, y: second.y };
second = { x, y };
const dx = first.x - second.x;
const dy = first.y - second.y;
const transStr = `translate(${dx}px, ${dy}px)`;
el.style.transform = transStr;
await sleep(0); // comment me out
el.style.transition = "transform .5s";
el.style.transform = "";
}
return (
<>
<div style={{ display: "flex", gap: "1rem", padding: "3rem"}}>
<div ref={ start ? boxRefCb : null } style={{ visibility: start ? "" : "hidden", width: 100, height: 100, border: "solid 1px grey" }}></div>
<div ref={ !start ? boxRefCb : null } style={{ visibility: !start ? "" : "hidden", width: 100, height: 100, border: "solid 1px grey" }}></div>
</div>
<button style={{ marginLeft: "3rem"}} onClick={() => setStart(start => !start)}>start | {start.toString()}</button>
</>
);
}
ReactDOM.render(<App />,
document.getElementById("root"))
I suspect this is some event loop magic that I don't understand. Could someone shed some light onto this for me please?
What happens is that the browser may have time to recalculate the CSSOM boxes (a.k.a "perform a reflow"), during that sleep. Without it, your transform rule isn't ever really applied.
Indeed, browsers will wait until it's really needed before applying the changes you made, and update the whole page box model, because doing so can be very expensive.
When you do something like
element.style.color = "red";
element.style.color = "yellow";
element.style.color = "green";
all the CSSOM will see is the latest state, "green". The other two are just discarded.
So in your code, when you don't let the event loop actually loop, the transStr value is never seen either.
However, relying on a 0ms setTimeout is a call to issues, there is nothing that does ensure that the styles will get recalculated at that time. Instead, it's better to force a recalc manually. Some DOM methods/properties will do so synchronously. But remember that a reflow can be a very expensive operation, so be sure to use it sporadically, and if you have multiple places in your code in need of this, be sure to concatenate them all so that a single reflow is performed.
const el = document.querySelector(".elem");
const move = () => {
el.style.transition = "";
const transStr = `translate(150px, 0px)`;
el.style.transform = transStr;
const forceReflow = document.querySelector("input").checked;
if (forceReflow) {
el.offsetWidth;
}
el.style.transition = "transform .5s";
el.style.transform = "";
}
document.querySelector("button").onclick = move;
.elem {
width: 100px;
height: 100px;
border: 1px solid grey;
}
.parent {
display: flex;
padding: 3rem;
}
<label><input type=checkbox checked>force reflow</label>
<button>move</button>
<div class=parent>
<div class=elem></div>
</div>
Or with OP's code.
You're approaching this problem with a vanilla JavaScript solution, but React uses a virtual DOM and expects DOM elements to be re-rendered when state has been changed. Therefore, I'd recommend leveraging React state to update the element's XY position within the virtual DOM, but while still using CSS.
Working demo here or the code can be found here:
import { useState, useRef, useLayoutEffect } from "react";
import "./styles.css";
type BoxXYPosition = { x: number; y: number };
export default function App() {
const startBox = useRef<HTMLDivElement | null>(null);
const startBoxPosition = useRef<BoxXYPosition>({ x: 0, y: 0 });
const endBox = useRef<HTMLDivElement | null>(null);
const [boxPosition, setBoxPosition] = useState<BoxXYPosition>({
x: 0,
y: 0
});
const { x, y } = boxPosition;
const hasMoved = Boolean(x || y);
const updatePosition = () => {
if (!endBox.current) return;
const { x: endX, y: endY } = endBox.current.getBoundingClientRect();
const { x: startX, y: startY } = startBoxPosition.current;
// "LAST" - calculate end position
const moveXPosition = endX - startX;
const moveYPosition = endY - startY;
// "INVERT" - recalculate position based upon current x,y coords
setBoxPosition((prevState) => ({
x: prevState.x !== moveXPosition ? moveXPosition : 0,
y: prevState.y !== moveYPosition ? moveYPosition : 0
}));
};
useLayoutEffect(() => {
// "FIRST" - save starting position
if (startBox.current) {
const { x, y } = startBox.current.getBoundingClientRect();
startBoxPosition.current = { x, y };
}
}, []);
// "PLAY" - switch between start and end animation via the x,y state and a style property
return (
<main className="app">
<h1>Transition Between Points</h1>
<div className="container">
<div
ref={startBox}
className="box start-point"
style={{
transform: hasMoved
? `translate(${x}px, ${y}px) rotateZ(360deg)`
: ""
}}
>
{hasMoved ? "End" : "Start"}
</div>
<div className="end-container">
<div ref={endBox} className="box end-point" />
</div>
</div>
<button
type="button"
onClick={updatePosition}
>
Move to {hasMoved ? "Start" : "End"}
</button>
</main>
);
}
I have developed blinking markups on my model viewer and used below code part for it. But, when I changed camera viewing on my model, the location of markups has been changed and they were located incorrectly. I want to hook them and not to change the state of them.
$('#mymk'+randomId).append('<svg id="mysvg'+randomId+ '"></svg>')
$('#mysvg'+randomId ).css({
'width': '50px'
});
var rad = 12;
const size=300;
var s = Snap($('#mysvg'+randomId)[0]);
s.attr({viewBox: "0 0 " + size + " " + size});
const userPath = s.path("M500,10C229.4,10,10,229.4,10,500c0,270.6,219.4,490,490,490c270.6,0,490-219.4,490-490S770.6,10,500,10z M496.2,255c70,0,126.7,56.7,126.7,126.7s-56.7,126.7-126.7,126.7s-126.7-56.7-126.7-126.7C369.5,311.7,426.3,255,496.2,255z M629.1,745h-266c-25.8,0-78.2-21.1-78.2-46.8v0c0-85.8,70.4-156,156.4-156h109.5c86,0,156.4,70.2,156.4,156C707.4,723.9,654.9,745,629.1,745z")
.attr({fill: "#029e02"});
const userBox = userPath.getBBox();
const circleBg = s.circle(userBox.cx, userBox.cy, userBox.width / 2)
.attr({fill: "#029e02", stroke: "#029e02", strokeWidth: 800});
const user = s.group(circleBg, userPath);
const userScaleFactor = 0.09;
const userMtx = new Snap.Matrix();
userMtx.translate(size/2 - userBox.width*userScaleFactor / 2, size/2 - userBox.height*userScaleFactor/2);
userMtx.scale(userScaleFactor);
user.attr({transform: userMtx, cursor: "pointer"});
const smallUserMtx = userMtx.clone().scale(0.9, 0.9, userBox.cx, userBox.cy);
setInterval(blink, 3000);
function blink(){
user.animate({transform: smallUserMtx}, 200, mina.easein, () => user.animate({transform: userMtx}, 200));
const newCircle = s.circle(size/2, size/2, 40);
user.before(newCircle);
newCircle.attr({fill: "rgb(244, 203, 217)", strokeWidth: "20", stroke: "#015401"});
newCircle.animate({r: size/2, opacity: 0}, 2000, mina.easeout, () => newCircle.remove());
};
user.attr({
pointerEvents: "auto",
cursor: "pointer"
});
In the viewer this is typically handled by locking the camera navigation, basically preventing the user from moving the camera while the overlay markups are displayed. You can use the setNavigationLock method to control the navigation.
I am pretty new at coding and I'm tinkering around with react. On my index.tsx section of React, I have a counter on the tab of the web page that counts by 5 and resets at 100. I'm wondering if there is a way to disable the counter and display something like "Rainbow", without deleting the code for the counter.
Basically like an on/off switch that will show "Rainbow when "off" and counter when "on".
Here is the code for the counter:
let counter = 1;
const intervalFunction = () => {
document.title = "Count is " + counter;
counter = counter + 5;
if (counter >= 100) {
counter = 0;
}
Add a variable switchFlag.
let switchFlag = false; // or true
let counter = 1;
const intervalFunction = () => {
document.title = switchFlag ? "Count is " + counter : "Rainbow";
counter = counter + 5;
if (counter >= 100) {
counter = 0;
}
try this way :
import React from 'react'
import { useState } from 'react'
const Stack = () => {
const [swith, setSwitch] = useState(true)
const [count, setCount] = useState(0)
const handleCounterIncrease = () => {
if (count < 100) {
setCount(count + 5)
}
else {
setCount(100)
}
}
const handleCounterDecrease = () => {
if (count > 5) {
setCount(count - 5)
}
else {
setCount(0)
}
}
const handleSwitchOff = () => {
setSwitch(false)
}
const handleSwitchOn = () => {
setSwitch(true)
}
return (
<div>
{
swith ?
<div>
<p>{count}</p>
<div style={{ display: "flex" }}>
<button onClick={handleCounterIncrease} style={{ backgroundColor: 'green', padding: '3px', marginRight: '20px' }}>Increase </button>
<button onClick={handleCounterDecrease} style={{ backgroundColor: 'red', padding: '3px' }}> Decrease</button>
</div>
</div>
: "Rainbow"
}
<div style={{ display: "flex", marginTop: '20px' }}>
<button onClick={handleSwitchOn} style={{ backgroundColor: 'green', padding: '3px', marginRight: '20px' }}>swith on </button>
<button onClick={handleSwitchOff} style={{ backgroundColor: 'red', padding: '3px' }}>swith off</button>
</div>
</div>
)
}
export default Stack
I have a problem that I can't solve. I want to use JointJS fromJSON function to reconstruct the flowchart from a JSON (previously exported using JoinJS's toJSON function.
The problem is that the call to the fromJSON function always returns the following error:
Whether I call it inside the hook mounted () or call it from the click of a button.
For completeness I also want to say that I am using Vue.js.
The code I'm using instead is the following:
<template>
<div class="wrapper">
<button v-on:click="getGraphJSON">Get graph JSON</button>
<button v-on:click="resetGraphJSON">Restore graph from JSON</button>
<div id="myholder"></div>
</div>
</template>
<script>
const _ = require('lodash')
const joint = require('jointjs')
const g = require('../../node_modules/jointjs/dist/geometry.js')
const backbone = require('../../node_modules/backbone/backbone.js')
const $ = require('../../node_modules/jquery/dist/jquery.js')
import '../../node_modules/jointjs/dist/joint.css';
var CustomRectangle = joint.shapes.standard.Rectangle.define('CustomRectangle', {
type: 'CustomRectangle',
attrs: {
body: {
rx: 10, // add a corner radius
ry: 10,
strokeWidth: 1,
fill: 'cornflowerblue'
},
label: {
textAnchor: 'left', // align text to left
refX: 10, // offset text from right edge of model bbox
fill: 'white',
fontSize: 18
}
}
}, {
markup: [{
tagName: 'rect',
selector: 'body',
}, {
tagName: 'text',
selector: 'label'
}]
}, {
createRandom: function() {
var rectangle = new this();
var fill = '#' + ('000000' + Math.floor(Math.random() * 16777215).toString(16)).slice(-6);
var stroke = '#' + ('000000' + Math.floor(Math.random() * 16777215).toString(16)).slice(-6);
var strokeWidth = Math.floor(Math.random() * 6);
var strokeDasharray = Math.floor(Math.random() * 6) + ' ' + Math.floor(Math.random() * 6);
var radius = Math.floor(Math.random() * 21);
rectangle.attr({
body: {
fill: fill,
stroke: stroke,
strokeWidth: strokeWidth,
strokeDasharray: strokeDasharray,
rx: radius,
ry: radius
},
label: { // ensure visibility on dark backgrounds
fill: 'black',
stroke: 'white',
strokeWidth: 1,
fontWeight: 'bold'
}
});
return rectangle;
}
});
export default {
name: 'JointChartRestorable',
data() {
return {
graph: null,
paper: null,
// graphJSON: JSON.parse('{"cells":[{"type":"standard.Rectangle","position":{"x":100,"y":30},"size":{"width":100,"height":40},"angle":0,"id":"049776c9-7b6d-4aaa-8b02-1edc3bea9852","z":1,"attrs":{"body":{"fill":"blue"},"label":{"fill":"white","text":"Rect #1"}}},{"type":"standard.Rectangle","position":{"x":400,"y":30},"size":{"width":100,"height":40},"angle":0,"id":"b6e77973-1195-4749-99e1-728549329b11","z":2,"attrs":{"body":{"fill":"#2C3E50","rx":5,"ry":5},"label":{"fontSize":18,"fill":"#3498DB","text":"Rect #2","fontWeight":"bold","fontVariant":"small-caps"}}},{"type":"standard.Link","source":{"id":"049776c9-7b6d-4aaa-8b02-1edc3bea9852"},"target":{"id":"b6e77973-1195-4749-99e1-728549329b11"},"id":"4ed8e3b3-55de-4ad2-b79e-d4848adc4a58","labels":[{"attrs":{"text":{"text":"Hello, World!"}}}],"z":3,"attrs":{"line":{"stroke":"blue","strokeWidth":1,"targetMarker":{"d":"M 10 -5 0 0 10 5 Z","stroke":"black","fill":"yellow"},"sourceMarker":{"type":"path","stroke":"black","fill":"red","d":"M 10 -5 0 0 10 5 Z"}}}}],"graphCustomProperty":true,"graphExportTime":1563951791966}')
// graphJSON: JSON.parse('{"cells":[{"type":"examples.CustomRectangle","position":{"x":90,"y":30},"size":{"width":100,"height":40},"angle":0,"id":"faa7f957-4691-4bb2-b907-b2054f7e07de","z":1,"attrs":{"body":{"fill":"blue"},"label":{"text":"Rect #1"}}}]}')
graphJSON: JSON.parse('{"cells":[{"type":"CustomRectangle","position":{"x":100,"y":30},"size":{"width":100,"height":40},"angle":0,"id":"f02da591-c03c-479f-88cf-55c291064ca8","z":1,"attrs":{"body":{"fill":"blue"},"label":{"text":"Rect #1"}}}]}')
};
},
methods: {
getGraphJSON: function() {
this.graphJSON = this.graph.toJSON();
console.log(JSON.stringify(this.graphJSON));
this.graph.get('graphCustomProperty'); // true
this.graph.get('graphExportTime');
},
resetGraphJSON: function() {
if(this.graphJSON !== undefined && this.graphJSON !== null && this.graphJSON !== '') {
this.graph.fromJSON(this.graphJSON);
// this.paper.model.set(this.graphJSON);
} else {
alert('Devi prima cliccare sul tasto "Get graph JSON" almeno una volta');
}
}
},
mounted() {
this.graph = new joint.dia.Graph();
this.graph.fromJSON(this.graphJSON);
// this.graph.set('graphCustomProperty', true);
// this.graph.set('graphExportTime', Date.now());
this.paper = new joint.dia.Paper({
el: document.getElementById('myholder'),
model: this.graph,
width: '100%',
height: 600,
gridSize: 10,
drawGrid: true,
background: {
color: 'rgba(0, 255, 0, 0.3)'
},
// interactive: false, // disable default interaction (e.g. dragging)
/*elementView: joint.dia.ElementView.extend({
pointerdblclick: function(evt, x, y) {
joint.dia.CellView.prototype.pointerdblclick.apply(this, arguments);
this.notify('element:pointerdblclick', evt, x, y);
this.model.remove();
}
}),
linkView: joint.dia.LinkView.extend({
pointerdblclick: function(evt, x, y) {
joint.dia.CellView.prototype.pointerdblclick.apply(this, arguments);
this.notify('link:pointerdblclick', evt, x, y);
this.model.remove();
}
})*/
});
/*this.paper.on('cell:pointerdblclick', function(cellView) {
var isElement = cellView.model.isElement();
var message = (isElement ? 'Element' : 'Link') + ' removed';
eventOutputLink.attr('label/text', message);
eventOutputLink.attr('body/visibility', 'visible');
eventOutputLink.attr('label/visibility', 'visible');
});*/
/***************************************************/
/************** GRAPH ELEMENT SAMPLE ***************/
/***************************************************/
// var rect = new joint.shapes.standard.Rectangle();
// var rect = new CustomRectangle();
// rect.position(100, 30);
// rect.resize(100, 40);
// rect.attr({
// body: {
// fill: 'blue'
// },
// label: {
// text: 'Rect #1',
// fill: 'white'
// }
// });
// rect.addTo(this.graph);
/***************************************************/
/************** GRAPH ELEMENT SAMPLE ***************/
/***************************************************/
}
}
</script>
Right now I'm using a custom element, previously defined, but I've also done tests using the standard Rectangle element of JointJS.
Can anyone tell me if I'm doing something wrong?
Many thanks in advance.
Markup object could not be found in element that's reason why this error is getting. After it's imported jointjs to the vueJS project through jointjs or rabbit dependency;
import * as joint from 'jointjs' or import * as joint from 'rabbit'
window.joint = joint;
joint should be adjusted as global in environment by using window.
First I am a beginner and this is my first app. I have been working on this app for several days and I am stuck. On the page remote_read-org-3.js I am creating buttons that are a list of states. These are being pulled from a mySQL database. This part is working. I need to pass the stateabbr to the next window when the button is clicked. The problem is it passes the last state in the list no matter which button I click.
This is remote_read-org.js It may not been the cleanest code but I am still working through the how to's
var currentWin = Ti.UI.currentWindow;
var view02 = Titanium.UI.createView({
top:0,
left:0,
height: '100%',
width: '100%',
backgroundImage: 'images/wcs_background_2.jpg',
})
var label01 = Titanium.UI.createLabel({
text: "US STATES",
top:25,
left:125,
height:'auto',
width:'175',
textAlign: "left",
font:{fontFamily:'Arial',fontWeight:'bold',fontSize:24},
color: "#1c1e3b",
})
var label02 = Titanium.UI.createLabel({
text: "Attachments",
top:50,
left: 125,
height:'24',
width:'150',
textAlign: "left",
font:{fontFamily:'Arial',fontWeight:'bold',fontSize:18},
color: "#1c1e3b",
})
var view01 = Titanium.UI.createView({
top:90,
left:70,
height: 375,
width: Ti.UI.FILL,
})
var currentWin = Ti.UI.currentWindow;
var sendit = Ti.Network.createHTTPClient();
sendit.open('GET', 'http://localhost/test/read.php');
sendit.send();
sendit.onload = function(){
var json = JSON.parse(this.responseText);
var json = json.states;
var dataArray = [];
var scroller = Ti.UI.createScrollView({
height: Ti.UI.FILL,
width: Ti.UI.FILL,
});
var brandView = Ti.UI.createView({ //Primary view for buttons
title: 'Hello',
top:0,
left:0,
height : Ti.UI.FILL,
width : Ti.UI.FILL,
contentHeight : "auto",
backgroundColor : "transparent",
layout : "horizontal",
horizontalBounce :false,
});
scroller.add(brandView);
view01.add(scroller);
var pos;
for( pos=0; pos < json.length; pos++){
dataArray.push({title:'' + json[pos].stateAbbr + ''});
// set the array to the tableView
var btn = Ti.UI.createButton({
title: json[pos].stateAbbr,
width: 60,
height: 70,
top: pos * 0, // space the buttons at 105
left: 2,
backgroundImage: 'images/state_icon.png',
MyID: json[pos].stateAbbr,
});
btn.addEventListener('click', function(e) {
var newWindow = Titanium.UI.createWindow({
url: 'remote_read_acc.js',
MyID: btn.MyID
});
newWindow.open(newWindow);
});
brandView.add(btn);
};
};
var brandView = Ti.UI.createView({
});
view02.add(view01);
view02.add(label01);
view02.add(label02);
currentWin.add(view02);
I need to pass the stateabbr to this new window
remote_read_acc.js
var currentWin = Ti.UI.currentWindow;
var view02 = Titanium.UI.createView({
top:0,
left:0,
height: '100%',
width: '100%',
backgroundImage: 'images/wcs_background_2.jpg',
})
var label01 = Titanium.UI.createLabel({
text: "US STATES",
top:25,
left:125,
height:'auto',
width:'175',
textAlign: "left",
font:{fontFamily:'Arial',fontWeight:'bold',fontSize:24},
color: "#1c1e3b",
})
var label02 = Titanium.UI.createLabel({
text: "Attachments",
top:50,
left: 125,
height:'24',
width:'150',
textAlign: "left",
font:{fontFamily:'Arial',fontWeight:'bold',fontSize:18},
color: "#1c1e3b",
})
var view01 = Titanium.UI.createView({
top:90,
left:90,
height: 375,
width: Ti.UI.FILL,
})
var currentWin = Ti.UI.currentWindow;
var sendit = Ti.Network.createHTTPClient();
sendit.open('GET', 'http://localhost/test/attachments.php');
sendit.send();
sendit.onload = function(){
var json = JSON.parse(this.responseText);
var json = json.attachments;
var dataArray = [];
var scroller = Ti.UI.createScrollView({
height: Ti.UI.FILL,
width: Ti.UI.FILL,
});
var brandView = Ti.UI.createView({ //Primary view for buttons
title: 'Hello',
top:0,
left:0,
height : Ti.UI.FILL,
width : Ti.UI.FILL,
contentHeight : "auto",
backgroundColor : "transparent",
layout : "horizontal",
horizontalBounce :false,
});
scroller.add(brandView);
view01.add(scroller);
var pos;
for( pos=0; pos < json.length; pos++){
dataArray.push({title:'' + json[pos].attachmentName + ''});
// set the array to the tableView
var btn = Ti.UI.createButton({
title: json[pos].attachmentName ,
width: 190,
height: 30,
top: pos * 0, // space the buttons at 105
left: 2,
MyID: json[pos].attachmentName,
});
btn.addEventListener('click', function(e) {
var newWindow = Titanium.UI.createWindow({
url: '',
});
newWindow.open(newWindow);
brandView.add(btn);
};
};
var brandView = Ti.UI.createView({
});
view02.add(view01);
view02.add(label01);
view02.add(label02);
currentWin.add(view02);
I also then need to use that stateabbr that I passed to query the dataArray to pull just the values from the array that matches the stateabbr variable. so I can display them on this page.
Any help would be greatly appreciated
It's passing the last state in the list cause you are adding event listeners in a for loop and ignoring JavaScript scope rules (they are not the easiest to understand that's for sure). So what is happening is that btn is always equal to the last btn you create. Instead try this, use the source attribute of every click event, this separates your inner scope from the outer scope of the for loop:
btn.addEventListener('click', function(e) {
var newWindow = Titanium.UI.createWindow({
url: 'remote_read_acc.js',
MyID: e.source.MyID // Get the actual button that was clicked
});
newWindow.open(newWindow);
});
To optimize this even further I would move the event listener function outside of the loop. Also, I wouldn't use the url attribute of the window, as that will open an entirely different JavaScript context, which is pretty heavyweight, instead try to use the require() CommonJS directive.