How can I re-position a canvas element dynamically with CSS - javascript

I've written a piece of code in React and with mouse-click, I position a canvas element, I change 3 style property of the canvas element with mouse-click. When the page refreshes I am getting this canvas elements position from the database and try to change it again according to data coming from the database however it doesnt change any property but if i try to change 1 property it changes, what might be causing this?
Here is the relevant part of my code:
function click(e) {
if(upgrade_status){
if ( material[x_cord + "" + y_cord] == null) {
if(neededmat>totalmats){
//
}else{
setStyleProp('left', x_cord*90+30);
setStyleProp('top', y_cord*120+150);
setStyleProp('backgroundColor', "white");
}
}else{
if(mat ===source){
setStyleProp('left', x_cord*90+30);
setStyleProp('top', y_cord*120+150);
setStyleProp('backgroundColor', "white");
}
if(neededmat>totalmats){
//
}else{
//
}
}else{
//errormessage1
}
}
}else{
//errormessage2
}
}
function Economy() {
ref = useRef();
ref2 = useRef();
ref_yazi = useRef();
const kullanici =useTimes()
console.log("kaƧkere")
setStyleProp = (prop, value) => {
setStyle({
...style,
[prop]: value
})
}
const [style, setStyle] = useState({
position: "absolute",
border: "0px solid red",
zIndex: "3",
width: "35px",
height: "25px",
backgroundColor:"transparent"
});
useInterval(() => {
setCount(count + 1);
}, 1000);
let [count, setCount] = useState(0);
update_details=kullanici.minebuildtime;
if ('undefined' !== typeof(update_details) && update_details !== "0") {
let d = new Date();
let currenttime = d.getTime();
timing_mat='';
timing_upgrade_value = '';
timing_mats_values = update_details.split("-");
[timing_cord_x,timing_cord_y,timing_mat,timing_upgrade_value,timing_time]= timing_mats_values;
let time_difference=timing_time-currenttime;
if(time_difference<0){
if(timing_upgrade_value ==="1"){
database.add(timing_cord_x,timing_cord_y,timing_mat,currentcordsandvalues,db,userUid,timing_time);
}else{
ctx.clearRect(timing_cord_x * 90, timing_cord_y * 120, 90, 120);
database.update(timing_cord_x,timing_cord_y,timing_mat,timing_upgrade_value,currentcordsandvalues,db,userUid,timing_time);
}
ctx_yazi.clearRect(timing_cord_x, timing_cord_y, 100, 30);
if(upgrade_status === false){
setStyleProp("backgroundColor", "transparent");
}
upgrade_status=true;
}else{
ctx_yazi.clearRect(timing_cord_x, timing_cord_y, 100, 30);
if(upgrade_status!==false){
//this is the part that doesnt work properly, only works if i use
//setStyleProp once
setStyleProp('position', 'absolute');
setStyleProp('backgroundColor', 'blue');
setStyleProp('left', timing_cord_x*90+30);
setStyleProp('top', timing_cord_y*120+150);
ctx_yazi.font = "14px Arial";
ctx_yazi.fillStyle = "black";
ctx_yazi.textAlign = "bottom";
}
ctx_yazi.fillText(Math.trunc(time_difference/1000), 5, 17);
upgrade_status=false;
}
}else{
upgrade_status=true;
}
return (
<>
{user ? (
<div className="screens">
<canvas
ref={ref2}
className="buildings"
width={800}
height={120}
onClick={e => {
}}
/>
<canvas
ref={ref}
className="gameScreen"
width={800}
height={600}
onClick={e => {
}}
/>
<canvas
ref={ref_yazi}
style={style}
width={30}
height={25}
/>
</div>
) : (<div>Please login</div>)}
</>
);
}

Okay after a bit struggle i managed get it work by doing a few changes. First of all i realised setStyleProp function under Economy function was changing only the last property of the style so i changed the function setStyleProp to accept 3 variables like below,
setStyleProp = (prop1, value1,prop2,value2,prop3,value3) => {
setStyle({
...style,
[prop1]: value1,
[prop2]: value2,
[prop3]: value3,
})
}
and then i changed the function call under Economy function like this;
setStyleProp('top', timing_cord_y*120+150,'left', timing_cord_x*90+30,'backgroundColor','white');
and it worked just like expected. The weird part i didnt understand i didnt change function calls at onClick events and they still work like below
setStyleProp('left', x_cord*90+30);
setStyleProp('top', y_cord*120+150);
setStyleProp('backgroundColor', "white");

Related

React video player progress bar for multiple videos behaves strange

I am creating a react application where I am using three videos with custom progress bar for each one to be displayed at screen. I am facing a problem with progress bar update for all the three videos. When one video is fully played it's progress works fine but as soon as second video start playing, It also updates the bar width together (video1 and video2). I am using "timeupdate" event for updating the video progress bar.
`
import * as React from 'react';
import './style.css';
export default function App() {
var v3 = `http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4`;
var v2 = `http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4`;
var v1 = `http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerFun.mp4`;
const [id, setId] = React.useState(1);
const [currentVideo, setCurrentVideo] = React.useState('');
const [barCompleted, setBarCompleted] = React.useState<any>({
myBar1: false,
myBar2: false,
myBar3: false,
});
const videoRef = React.useRef<any>();
const progress1 = React.useRef<any>();
const progress2 = React.useRef<any>();
const progress3 = React.useRef<any>();
const myBar1 = React.useRef<any>();
const myBar2 = React.useRef<any>();
const myBar3 = React.useRef<any>();
const handleOnEnded = () => {
videoRef.current.removeEventListener('timeupdate', () => {});
// setBarCompleted(prev=>{myBar1:true,...prev})
setId((prev) => prev + 1);
};
const getVideoById = (id) => {
if (id == 1) {
setCurrentVideo(v1);
} else if (id == 2) {
setCurrentVideo(v2);
} else if (id == 3) {
setCurrentVideo(v3);
}
};
React.useEffect(() => {
getVideoById(id);
}, [id]);
React.useEffect(() => {
videoRef?.current?.load();
configureProgress(id);
}, [currentVideo, id]);
const configureProgress = (id) => {
videoRef?.current?.addEventListener('timeupdate', (e) => {
// console.log(e.target.currentTime);
let i = 0;
if (i == 0) {
i = 1;
// var elem = document.getElementById('myBar1');
let width = 1;
let sid = setInterval(frame, 1000);
function frame() {
if (videoRef.current.ended || width >= 100) {
clearInterval(sid);
i = 0;
if (id == 1) {
progress1.current.style.backgroundColor = 'green';
myBar1.current.style.width = 0 + '%';
} else if (id == 2) {
progress2.current.style.backgroundColor = 'green';
myBar2.current.style.width = 0 + '%';
} else if (id == 3) {
progress3.current.style.backgroundColor = 'green';
myBar3.current.style.width = 0 + '%';
}
} else {
width = Math.ceil(
(videoRef.current.currentTime / videoRef.current.duration) * 100
);
if (id == 1) {
myBar1.current.style.width = width + '%';
console.log('width update id1');
} else if (id == 2) {
myBar2.current.style.width = width + '%';
console.log('width update id2');
} else if (id == 3) {
myBar3.current.style.width = width + '%';
console.log('width update id3');
}
}
}
}
});
};
return (
<div>
<div className="progress-container">
<div id="myProgress1" ref={progress1}>
<div id="myBar1" ref={myBar1}></div>
</div>
<div id="myProgress2" ref={progress2}>
<div id="myBar2" ref={myBar2}></div>
</div>
<div id="myProgress3" ref={progress3}>
<div id="myBar3" ref={myBar3}></div>
</div>
</div>
{currentVideo && (
<video
autoPlay={true}
controls
muted={true}
id="myVideo1"
width={300}
height={250}
ref={videoRef}
crossOrigin="anonymous"
onEnded={handleOnEnded}
preload="metadata"
playsInline
>
<source src={currentVideo} type="video/mp4" />
</video>
)}
</div>
);
}
`
.progress-container {
display: flex;
}
#myProgress1,
#myProgress2,
#myProgress3 {
width: 33%;
background-color: #ddd;
}
#myBar1,
#myBar2,
#myBar3 {
width: 1%;
height: 30px;
background-color: #04aa6d;
}
[enter image description here](https://i.stack.imgur.com/usYQq.png)
I am expecting that "timeupdate" event should not run for second video as it also updates width for previous video(1st video).

How to set new value in two dimensional array with useState()

See here for a Codesandbox displaying my problem. Try typing a few words in both the textareas to see what is going wrong.
I'm new to React. I am trying to dynamically create a svg element based on user input. Im splitting up the individual words, calculating the width of these words, and push them into a content array.
Without using the useState React hook (left side on Codesandbox), this all works fine. I just declared a variable const content = [[]] and push the individual words to the array. However, for some reason when using useState (const [content, setContent] = useState([[]])), the array is updated totally different.
How can I make my (left) example from the Codesandbox example work with the React useState hook (right example from Codesandbox) and what am I doing wrong?
So I fixed your code the way you have it working without using useState hook for the content. I created two new states and used useEffect as well which felt necessary.
When state changes component re-renders, so you need to be careful about the state changes, in your case you don't need to copy the state of content into the newContent instead you need to re-initialize it same as you did at the top when initializing the state.
Similarly you also need to use useEffect hook where you can run a part of code which is dependent on some variable in your case it's dummyText, wordsWithComputedWidth, spaceWidth on separate locations.
import { useState, useEffect } from "react";
export default function WithUseState() {
const [dummyText, setDummyText] = useState("");
const [wordsWithComputedWidth, setWordsWithComputedWidth] = useState(1);
const [spaceWidth, setSpaceWidth] = useState(0);
const [content, setContent] = useState([[]]);
function calculateWordWidths(v) {
const words = v.split(/\s+/);
let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
let text = document.createElementNS("http://www.w3.org/2000/svg", "text");
text.style =
"font: 10pt LiberationSans, Helvetica, Arial, sans-serif; white-space: pre;";
svg.appendChild(text);
document.body.appendChild(svg);
const wordsWithComputedWidth = words.map((word) => {
text.textContent = word;
return { word, width: text.getComputedTextLength() };
});
text.textContent = "\u00A0"; // Unicode space
const spaceWidth = text.getComputedTextLength();
document.body.removeChild(svg);
return { wordsWithComputedWidth, spaceWidth };
}
useEffect(() => {
let { wordsWithComputedWidth, spaceWidth } = calculateWordWidths(dummyText);
setWordsWithComputedWidth(wordsWithComputedWidth);
setSpaceWidth(spaceWidth);
}, [dummyText]);
useEffect(() => {
let xPos = 0;
let yPos = 16;
let page = 1;
let i = 0;
let len = wordsWithComputedWidth.length;
let newContent = [[]];
while (i < len) {
if (yPos > 25) {
newContent.push([]);
page++;
xPos = 0;
yPos = 16;
}
newContent[page - 1].push(
<text
x={xPos}
y={yPos}
key={i + xPos + yPos + wordsWithComputedWidth[i].word}
style={{
font: "10pt LiberationSans, Helvetica, Arial, sans-serif",
whiteSpace: "pre"
}}
>
{wordsWithComputedWidth[i].word}
</text>
);
xPos += wordsWithComputedWidth[i].width + spaceWidth;
if (xPos > 25) {
yPos += 16;
xPos = 0;
}
i++;
}
setContent(newContent);
}, [wordsWithComputedWidth, spaceWidth]);
function renderContentStructure() {
return content.map((page, pageIdx) => {
return (
<>
<span style={{ fontWeight: "bold" }}>Page: {pageIdx}</span>
<ol key={pageIdx}>
{page.map((fields, fieldIdx) => {
return <li key={fieldIdx}> {fields} </li>;
})}
</ol>
</>
);
});
}
return (
<div className="with-use-state">
<div>
Currently NOT working <strong>With</strong> useState
</div>
<div style={{ marginTop: "50px" }}>
<div className="content">
<div>Content</div>
<textarea
rows="6"
cols="24"
onChange={(e) => setDummyText(e.target.value)}
placeholder="type here..."
/>
</div>
<div className="preview">
<div>Preview</div>
<>
{content.map((page, idx) => (
<svg
viewBox="0 0 50 50"
xmlns="http://www.w3.org/2000/svg"
style={{
border: "1px solid #aaa",
width: "100px",
display: "block"
}}
key={idx}
>
{page}
</svg>
))}
</>
</div>
<div>{renderContentStructure()}</div>
</div>
</div>
);
}

Cannot read property 'style' of undefined in useEffect

Currently I'm trying to change the layout of my webpage according to the width of the webpage. So if it is higher that a certain pixels, it should show a different view, but if its lower, then it should show a different view. To achieve this I have tried using useState and useEffect to get the window.innerWidth and then placing conditions on my return statement but this brings up a Cannot read property 'style' of undefined.
Code:
export default function App() {
const [screen, setScreen] = useState(false);
const [width, setWidth] = useState(0);
useEffect(() => {
setWidth(window.innerWidth);
});
const ref = useRef(null);
// Reduce value if want the image to be closer to the edges
// otherwise to the center
const setImageLimitMovement = 1;
const setTextLimitMovement = 4;
const opacityRange = 400;
// Speed text movement
const speed = 1; // .5
useEffect(() => {
window.addEventListener("resize", () => {
if (window.innerWidth !== 0) {
setScreen(window.innerWidth);
}
});
}, []);
useEffect(() => {
const app = [...ref.current.children];
const titles = app.filter((el) => el.matches(".titles") && el);
const blocks = app.filter((el) => el.matches(".blocks") && el);
const img = app.find((el) => el.matches("#passport") && el);
// Get the center point of blocks in an array
const centerPoints = blocks.map((blockEl, idx) => {
const blockindex = idx + 1;
const blockHeight = Math.floor(blockEl.getBoundingClientRect().height);
const blockHalf = blockHeight / 2;
return blockHeight * blockindex - blockHalf;
});
const leftMoveLimitImg = -centerPoints[0] / setImageLimitMovement;
const rightMoveLimitImg = centerPoints[0] / setImageLimitMovement;
const textLimit = centerPoints[0] / setTextLimitMovement;
const changeBackground = () => {
const value = window.scrollY;
titles[0].style.transform = `translateY(-${value * speed}px)`;
// IMAGE BOUNCE
// Move to <==
if (centerPoints[0] > value) {
img.style.transform = `translateX(-${
value * (1 / setImageLimitMovement)
}px)`;
titles[1].style.transform = `translateX( ${
0 + value / setTextLimitMovement
}px)`;
titles[1].style.opacity = value / opacityRange;
return;
}
window.requestAnimationFrame(changeBackground);
};
window.addEventListener("scroll", changeBackground);
return () => window.removeEventListener("scroll", changeBackground);
}, [screen]);
return (
<>
{/* <div style={{height:"100vh"}}></div> */}
{width > 650 && (
<div id="section2">
<main ref={ref}>
<h1 id="title" className="titles">
{posts.Title}
</h1>
<section id="block1" className="blocks"></section>
<figure id="passport">
<img
alt="passport"
src="https://cdn.britannica.com/87/122087-050-1C269E8D/Cover-passport.jpg"
/>
</figure>
<h2 id="text1" className="titles text1">
Random Text 1
</h2>
</main>
</div>
)}
{width < 649 && (
<>
<div style={{ height: "100vh", backgroundColor: "black" }}></div>
</>
)}
{/* Stop Scrolling Animation */}
{/* <div>Content</div> */}
</>
);
}
The problem as identified by #lawrence-witt is because the ref object is not yet set when the useEffect runs the first time.
Here is the codesandbox link https://codesandbox.io/s/infallible-lamarr-usim6
I added some comments as I did a bit of refactor, but please feel free to pick what solves your problem.

OnScroll Event is cycling when scrolled programmicaly inside

When I create a Scroll Listener which scrolls to specific position inside, it triggers itself again even when I catch the first recall at the beginning. Idk what could cause this.
const Example: React.FC<Props> = () => {
let _preventScroll = false;
let _currentParagraph = 0;
let _lastScrollTop = 0;
const _paragraphs = [
useRef<HTMLDivElement>(null),
useRef<HTMLDivElement>(null),
useRef<HTMLDivElement>(null),
useRef<HTMLDivElement>(null),
useRef<HTMLDivElement>(null),
useRef<HTMLDivElement>(null),
];
useEffect(() => {
const onScroll = (ev: Event) => {
if (_preventScroll) {
ev.preventDefault();
_preventScroll = false;
return;
}
var st = window.pageYOffset || document.documentElement.scrollTop;
if (st > _lastScrollTop && _currentParagraph < 5) {
_currentParagraph++;
} else if (_currentParagraph > 0) {
_currentParagraph--;
}
_preventScroll = true;
_paragraphs[_currentParagraph].current?.scrollIntoView({
behavior: "smooth",
block: "start",
});
_lastScrollTop = st <= 0 ? 0 : st;
};
window.addEventListener("scroll", onScroll);
return () => {
window.removeEventListener("scroll", onScroll);
};
}, []);
return (
<>
<div style={{height: "100vh"}} ref={paragraph[0]} />
<div style={{height: "100vh"}} ref={paragraph[1]} />
<div style={{height: "100vh"}} ref={paragraph[2]} />
<div style={{height: "100vh"}} ref={paragraph[3]} />
<div style={{height: "100vh"}} ref={paragraph[4]} />
</>
);
};
export default Example;
I tried to add an Catch of the first recall but after this another happen from nowhere.
May there is a Issue with Bubbling idk.

Logic for rotating a square

The following code is using React.
I have a square positioned at 45deg, that has four elements. Upon click on each element, I want the square to rotate the relevant degrees so that the clicked element is on top. (In the image, 3 is currently selected).
Please see the notes within the code. I hope it explains what my logic is.
Here is the component code:
class CentreCtrls extends React.Component {
constructor(props) {
super(props);
this.state = {
aboutUs: {
activeCtrl: 0
}
}
}
// animate the square to rotate the relevant degrees
// so that the clicked element is at the top.
// this logic is flawed and not working correctly
componentDidUpdate() {
const activeHeading = this.state.aboutUs.activeCtrl;
const { centreCtrl } = this.refs;
const style = centreCtrl.style;
const currentDeg = parseInt(style.transform.slice(7, -4), 10);
const position = this.getPosition(activeHeading, 0);
const degRotation = position * 90;
console.log('pos:', position, 'deg:', currentDeg, '-', degRotation);
anime({
targets: this.refs.centreCtrl,
rotate: `-${currentDeg + degRotation}deg`,
duration: 150,
easing: 'linear',
});
}
onClickHandler = e =>
const ele = e.target;
const i = ele.parentNode.getAttribute('data-i');
this.setState({
aboutUs: {
activeCtrl: parseInt(i, 10),
},
});
};
// the purpose of this function is to find the current value
// to be assigned to the 'data-i' property on element, this 'data-i'
// is read in the above 'componentDidUpdate' function to animate
// the square the relevant degrees so that the clicked square is at
// top (where the purple 3 is in the image).
getPosition = (i, activeHeading) => {
const total = 3; // start: 0, ttl: 4
let correctSeq = i;
if (i === activeHeading) {
correctSeq = 0;
// if i == 2, then for our rotation purpose, its at position 3 in DOM
// because of the way, DOM renders elements from left to right within
// square
} else if (i === 2) {
correctSeq = 3;
} else if (i === 3) {
correctSeq = 2;
}
let pos = total - activeHeading + (correctSeq + 0);
if (pos > 3) {
pos = pos - 3;
}
return pos;
};
render() {
const { data } = this.props;
const activeHeading = this.state.aboutUs.activeCtrl;
return (
// using the react v15 style ref
<div
className="centreCtrl"
ref="centreCtrl"
style={{ transform: 'rotate(45deg)' }}
>
{data.map((entry, i) => {
const j = this.getPosition(i, activeHeading);
return (
<div
className="ctrl"
key={i}
data-i={j}
id={`${entry.narrative.heading}`}
onClick={this.onClickHandler}
>
<div
className="textContainer"
id={entry.narrative.heading}
ref={`textRef${i}`}
>
{j}
</div>
</div>
);
})}
</div>
);
}
}
Edit: How can I get the rotation logic to work correctly. More to the point, I am looking for input into what is the best logic for rotation calculation of this square. It looks far easier than I estimated.
Assuming you want to rotate clockwise, something like this might help:
import React from "react";
import ReactDOM from "react-dom";
import styled from "styled-components";
import { Container, Square, Block, Top, Bottom } from './components';
const rotateRight = items => {
const last = items.pop();
items.unshift(last);
};
class App extends React.Component {
state = {
items: ["a", "b", "c", "d"],
rotation: 0,
};
handleClick = i => {
let start = i;
let count = 0;
let items = [...this.state.items];
const end = items.length - 1;
while (start <= end) {
rotateRight(items);
start += 1;
count += 1;
}
this.setState(state => ({
rotation: ((count * 90) % 360),
}));
};
render() {
const { items, rotation } = this.state;
return (
<Container>
<Square rotation={rotation}>
<Top>
<Block onClick={() => this.handleClick(0)}>
{items[0]}
</Block>
<Block onClick={() => this.handleClick(1)}>
{items[1]}
</Block>
</Top>
<Bottom>
<Block onClick={() => this.handleClick(2)}>{
items[2]}
</Block>
<Block onClick={() => this.handleClick(3)}>
{items[3]}
</Block>
</Bottom>
</Square>
</Container>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
StackBlitz link here.

Categories