The issue with drawing straight line and rectangle in Konvajs React - reactjs

I wanted to create drawing app in React and to achieve that I came across something called Konvajs. Then, I started and somehow achieved that I can draw non-straight lines like this
But what I wanted to draw is straight lines like this
and rectangle. Here is my code:
import React, { Component } from "react";
import { Stage, Layer, Line, Rect, Text } from "react-konva";
export default class Canvas extends Component {
state = {
lines: [],
};
handleMouseDown = () => {
this._drawing = true;
this.setState({
lines: [...this.state.lines, []],
});
};
handleMouseUp = () => {
this._drawing = false;
};
handleMouseMove = (e) => {
if (!this._drawing) {
return;
}
const stage = this.stageRef.getStage();
const point = stage.getPointerPosition();
const { lines } = this.state;
let lastLine = lines[lines.length - 1];
lastLine = lastLine.concat([point.x, point.y]);
lines.splice(lines.length - 1, 1, lastLine);
this.setState({
lines: lines.concat(),
});
};
render() {
return (
<Stage
width={window.innerWidth}
height={window.innerHeight}
onContentMousedown={this.handleMouseDown}
onContentMousemove={this.handleMouseMove}
onContentMouseup={this.handleMouseUp}
ref={(node) => {
this.stageRef = node;
}}
>
<Layer>
<Text text="Draw a thing!" />
{this.state.lines.map((line, i) => (
<Line key={i} points={line} stroke="red" />
))}
</Layer>
</Stage>
);
}
}

For straight lines replace this
lastLine = lastLine.concat([point.x, point.y]);
With this
lastLine = [lastLine[0], lastLine[1], point.x, point.y]
Basically, you should have maximum four points. The first two are created OnMouseDown and the last two should be updated as you are moving the cursor - OnMouseMove

Related

How to use the code above in React js using hooks

document.getElementById("cards").onmousemove = e => {
for(const card of document.getElementsByClassName("card")) {
const rect = card.getBoundingClientRect(),
x = e.clientX - rect.left,
y = e.clientY - rect.top;
card.style.setProperty("--mouse-x", `${x}px`);
card.style.setProperty("--mouse-y", `${y}px`);
};
}
I actually don't know how to use the above code in react js. so, if anyone knows please respond!
full source code link:
https://codepen.io/Hyperplexed/pen/MWQeYLW
to use Hook you need to handle with reference of element like this
const CardRef = React.useRef(null);
useShadow(CardRef);
return <div ref={CardRef} className="card" ></div>
And the hook would be something like this
import { useEffect } from 'react';
const useShadow = (reference: React.MutableRefObject<any>) => {
useEffect(() => {
const eventReference = (e) => {
const rect = reference.current.getBoundingClientRect(),
x = e.clientX - rect.left,
y = e.clientY - rect.top;
reference.current.style.setProperty('--mouse-x', `${x}px`);
reference.current.style.setProperty('--mouse-y', `${y}px`);
};
if (reference.current) {
const { current } = reference;
current.addEventListener('mousemove', eventReference);
}
return () => {
reference.current &&
reference.current.removeEventListener('mousemove', eventReference);
};
}, [reference]);
};
export default useShadow;
First of all, React does provide SyntheticEvents, so your onmousemove would probably look like this in React:
<div onMouseMove={ yourEventHandler } />
I can see what you are trying to do is to set the children .card's properties when the mouse had moved. What you can do is to have useState() in the parent .cards container to store the latest mouse position, then pass that state as props into the children. Something like:
export function Cards() {
const [mouseX, setMouseX] = useState(0);
const [mouseY, setMouseY] = useState(0);
const myOnMouseMove = (e)=> {
// set your state using setMouseX(), setMouseY()
}
return (
<div className='cards' onMouseMove={myOnMouseMove}>
<Card className='card' mouseX={mouseX} mouseY={mouseY} />
<Card className='card' mouseX={mouseX} mouseY={mouseY} />
...
</div>
)
}
(Not real implementation, just the concept)

When a video had stopped, other video stopped simultaneously in react native video

I have developed react-native cli, react-native-video (Currently, tried to expo-video).
In a screen, there are one flatList, it has a renderItem(column2). In a renderItem renders image or video thumbnail. Like shopping app UI AMAZON.
The problem is videos. It will stops automatically by user's swipe down to phone's view. Simultaneously, below stopped video, other video player will stops. This video is in exactly middle of user's phone view!
How can i solve this?
FlatList,
const [videoPlayback, setVideoPlayback] = useState(false);
const viewabilityConfig = {
itemVisiblePercentThreshold: 80,
};
const _onViewableItemsChanged = useCallback((props) => {
const changed = props.changed;
changed.forEach((item) => {
if (item.item.vimeoId) {
if (item.isViewable) {
setVideoPlayback((prev) => true);
} else {
setVideoPlayback((prev) => false);
}
}
});
}, []);
...
<FlatList
ref={ref}
numColumns={2}
data={posts}
keyExtractor={(item: any) => `${item.postId}`}
viewabilityConfig={viewabilityConfig}
onViewableItemsChanged={_onViewableItemsChanged}
renderItem={({ item, index }) => (
<PostContainer item={item} videoPlayback={videoPlayback} navigation={navigation} index={index} />
)}
PostContainer
const PostContainer = ({ item, videoPlayback }) => {
const videoPlayer = useRef(null);
useFocusEffect(
React.useCallback(() => {
videoPlayer?.current?.seek(0);
if (videoPlayer && !videoPlayback) {
videoPlayer?.current?.seek(0);
}
}, [fadeAnim, videoPlayback]),
);
return (
<View>
{ // This place that image component rendered}
{item.vimeoId && (
<StyledVideo
ref={videoPlayer}
paused={!videoPlayback}
repeat={true}
resizeMode={"cover"}
volume={0}
source={{
uri: item.vimeoThumbnail,
}}
/>
)}
</View>
);
};
All the videos in your list share the same videoPlayback state. If you want each video to be controlled individually, move your videoPlayback state logic into your PostContainer.
I solved my problem. There are no fault in my logic. the important thing is 'What videos are current playing?'
FlatList
const _onViewableItemsChanged = useCallback(
(props) => {
const changed = props.changed;
changed.forEach((item) => {
if (item.item.vimeoId) {
const tempId = item.item.vimeoId;
if (item.isViewable) {
if (readVideosVar.list.indexOf(tempId) === -1) {
dispatch(postSlice.actions.addId(tempId));
}
} else {
dispatch(postSlice.actions.deleteId(tempId));
}
}
});
},
[dispatch],
);
redux
addId: (state, action) => {
if (state.list.indexOf(action.payload) !== -1) {
return;
} else {
let tempA = [];
tempA.push(action.payload);
state.list = state.list.concat(tempA);
}
},
deleteId: (state, action) => {
if (state.list.indexOf(action.payload) === -1) {
return;
} else {
let stand = state.list;
let findIdx = stand.indexOf(action.payload);
let leftA = stand.slice(0, findIdx);
let rightA = stand.slice(findIdx + 1);
state.list = leftA.concat(rightA);
}
},
Compared to the existing code, I had added 'redux-toolkit'. In dispatch logic, "onViewalbeItems" had tracked chagend viewable items by real-time.
And then, I also added,
export const viewabilityConfig = {
minimumViewTime: 1000,
viewAreaCoveragePercentThreshold: 0,
};
This is just sugar.
I solved it like this !

Edit a single polygon in react leaflet

So I'm using https://github.com/alex3165/react-leaflet-draw
I want to make some particular polygon editable when you click a pencil button outside of the map. Right now there is a button "edit layers" but it naturally makes all of them editable, can I do it on polygon by polygon basis?
So I would want to do smth like this:
map to edit polygons
I opened an issue https://github.com/alex3165/react-leaflet-draw/issues/79 but not much success there
The thing is through refs I can get a giant circular object: this is what I get when I console.log .drawControl._toolbars.edit._modes.edit.handler.options.featureGroup._layers[57]
One option to consider is to create editable layer per every shape(feature), in a way that instead of creating EditControl control for a collection of features:
<FeatureGroup>
<EditControl
position="topright"
/>
</FeatureGroup>
create EditControl instance per every feature from GeoJSON:
<FeatureGroup>
<EditControl
position="topright"
/>
</FeatureGroup>
...
<FeatureGroup>
<EditControl
position="topright"
/>
</FeatureGroup>
The following example demonstrates this approach where:
only a single shape could be edited at a time
switching from one editable layer to another occurs on layer click
Example:
function EditableGroup(props) {
const [selectedLayerIndex, setSelectedLayerIndex] = useState(0);
function handleLayerClick(e) {
setSelectedLayerIndex(e.target.feature.properties.editLayerId);
}
let dataLayer = new L.GeoJSON(props.data);
let layers = [];
let i = 0;
dataLayer.eachLayer((layer) => {
layer.feature.properties.editLayerId = i;
layers.push(layer);
i++;
});
return (
<div>
{layers.map((layer, i) => {
return (
<EditableLayer
key={i}
layer={layer}
showDrawControl={i === selectedLayerIndex}
onLayerClicked={handleLayerClick}
/>
);
})}
</div>
);
}
where
function EditableLayer(props) {
const leaflet = useLeaflet();
const editLayerRef = React.useRef();
let drawControlRef = React.useRef();
let {map} = leaflet;
useEffect(() => {
if (!props.showDrawControl) {
map.removeControl(drawControlRef.current);
} else {
map.addControl(drawControlRef.current);
}
editLayerRef.current.leafletElement.clearLayers();
editLayerRef.current.leafletElement.addLayer(props.layer);
props.layer.on("click", function (e) {
props.onLayerClicked(e, drawControlRef.current);
});
}, [props, map]);
function onMounted(ctl) {
drawControlRef.current = ctl;
}
return (
<div>
<FeatureGroup ref={editLayerRef}>
<EditControl
position="topright"
onMounted={onMounted}
{...props}
/>
</FeatureGroup>
</div>
);
}
Here is a demo for the reference
const [mapLayers, setMapLayers] = useState([]);
const _onEdited = (e) => {
e.layers.eachLayer(a => {
const { _leaflet_id } = a;
setMapLayers((layers) => [
...layers,
{ id: _leaflet_id, latlngs: a.getLatLngs()[0] },
]);
});
}

React Konva - Performance of large number of nodes

I am trying to draw large number of nodes in React Konva based on this performance demo.
My code:
const ZoomTest = (props) => {
let width = window.innerWidth;
let height = window.innerHeight;
const [rects, setRects] = useState([])
const [node, setNode] = useState(null)
function makeRects () {
for (let x=0; x<10000; x++) {
var color = colors[colorIndex++];
if (colorIndex >= colors.length) {
colorIndex = 0;
}
let randX = Math.random() * width;
let randY = Math.random() * height;
rects.push(<Circle
id={x}
x={randX}
y={randY}
width={20}
height={20}
fill={color}
stroke={'blue'}
strokeWidth={1}
shadowBlur={0}
/>)
}
setRects(rects)
}
function getRects() {
if (rects.length === 0)
makeRects()
return rects
}
function tooltip() {
if (node === null)
return null
return <Label x={node.x} y={node.y} opacity={0.75}>
<Tag fill={'black'} pointerDirection={'down'} pointerWidth={10} pointerHeight={10} lineJoin={'round'} shadowColor={'black'}
shadowBlur={10} shadowOffsetX={10} shadowOffsetY={10} shadowOpacity={0.2}/>
<Text text={node.text} fill={'white'} fontSize={18} padding={5} />
</Label>
}
function onMouseOver(evt) {
var node = evt.target;
if (node) {
// update tooltip
var mousePos = node.getStage().getPointerPosition();
setNode({ x: mousePos.x, y: mousePos.y, text: node.id() })
console.log('node id = ', node.id())
}
}
return (
<Stage
width={window.innerWidth}
height={window.innerHeight}
draggable='false'
onMouseOver={onMouseOver}
onMouseMove={onMouseOver}
onDragMove={onMouseOver}
>
<Layer>
{getRects()}
</Layer>
<Layer>
{tooltip()}
</Layer>
</Stage>
)
}
The issue is that tooltip is very slow as compared to the demo. I think this is due to re-renders of the component on every mouse event and this is causing the lag.
Any idea how to make the react-konva performance similar to konvajs?
There is nothing special about react-konva. It is just React performance. In your render function, you have a large list of elements. Every time your state changes (on every mouseover) the react have to compare old structure with the new one. As you have many elements, it takes time to React to check it.
I think there are many ways to solve the issue. As one solution you can just move large list into another component and use React.memo to tell React that you don't need to recheck the whole list every time.
import React from "react";
import { render } from "react-dom";
import { Stage, Layer, Circle, Label, Tag, Text } from "react-konva";
var colors = ["red", "orange", "cyan", "green", "blue", "purple"];
const Rects = React.memo(({ rects }) => {
return (
<React.Fragment>
{rects.map(rect => (
<Circle
key={rect.id}
id={rect.id}
x={rect.x}
y={rect.y}
width={20}
height={20}
fill={rect.color}
stroke={"blue"}
strokeWidth={1}
shadowBlur={0}
/>
))}
</React.Fragment>
);
});
function makeRects() {
let width = window.innerWidth;
let height = window.innerHeight;
const rects = [];
let colorIndex = 0;
for (let x = 0; x < 10000; x++) {
var color = colors[colorIndex++];
if (colorIndex >= colors.length) {
colorIndex = 0;
}
let randX = Math.random() * width;
let randY = Math.random() * height;
rects.push({ id: x, x: randX, y: randY, color });
}
return rects;
}
const INITIAL = makeRects();
const App = props => {
const [rects, setRects] = React.useState(INITIAL);
const [node, setNode] = React.useState(null);
function tooltip() {
if (node === null) return null;
return (
<Label x={node.x} y={node.y} opacity={0.75}>
<Tag
fill={"black"}
pointerDirection={"down"}
pointerWidth={10}
pointerHeight={10}
lineJoin={"round"}
shadowColor={"black"}
shadowBlur={10}
shadowOffsetX={10}
shadowOffsetY={10}
shadowOpacity={0.2}
/>
<Text text={node.text} fill={"white"} fontSize={18} padding={5} />
</Label>
);
}
function onMouseOver(evt) {
var node = evt.target;
if (node) {
// update tooltip
var mousePos = node.getStage().getPointerPosition();
setNode({ x: mousePos.x, y: mousePos.y, text: node.id() });
console.log("node id = ", node.id());
}
}
return (
<Stage
width={window.innerWidth}
height={window.innerHeight}
draggable="false"
onMouseOver={onMouseOver}
onMouseMove={onMouseOver}
onDragMove={onMouseOver}
>
<Layer>
<Rects rects={rects} />
</Layer>
<Layer>{tooltip()}</Layer>
</Stage>
);
};
render(<App />, document.getElementById("root"));
https://codesandbox.io/s/react-konva-performance-on-many-elements-y680w?file=/src/index.js

Updating the state in a react component with canvas object fails

We have a few regions on a world map that will highlight when the user puts their mouse over that region. My approach to the problem is to use the canvas element and draw rough polygons around the regions and using some MIT code detect whether the user has their mouse inside the polygon. If they do then the image will switch to the appropriate graphic that shows the highlighted and labeled region.
Everything seemed to be going okay, but when I try to change the state it bombs out. I'm figuring it has something to do with the vitual dom somewhere.
Here is the component
class HomeMap extends Component {
constructor(props) {
super(props);
this.onMouseMove = this.onMouseMove.bind(this);
this.state = {
placeName: 'default',
};
}
onMouseMove(canvas, eX, eY) {
const { top, left } = canvas.getBoundingClientRect();
const [x, y] = [eX - left, eY - top];
const mouseOver = mapImages.map(image => ({
...image,
isInside: inside([x, y], image.polygon),
}))
.filter(({ isInside }) => isInside);
const stateChange = {
placeName: mouseOver.length === 0
? 'default'
: mouseOver[0].name,
};
console.log(stateChange);
this.setState(stateChange);
}
componentDidMount() {
this.ctx.drawImage(getImage('default').image, 0, 0, 656, 327);
}
componentDidUpdate() {
console.log(getImage('default'))
console.log(this.state);
this.ctx.drawImage(getImage(this.state.placeName).image, 0, 0, 656, 327);
}
render() {
return (
<div>
<canvas
onMouseMove={(e) => { this.onMouseMove(this.canvas, e.clientX, e.clientY); } }
width={626}
height={327}
ref={(ref) => {
this.ctx = ref.getContext('2d');
this.canvas = ref;
} }
/>
</div>
);
}
};
here is a fairly minimal reproduction running on codesandbox
When I run it in production when the component updates the canvas ref becomes null, I believe that is what throws the error.
I found the cause of the problem: caching the 2d context.
<canvas
onMouseMove={(e) => { this.onMouseMove(this.canvas, e.clientX, e.clientY); } }
width={626}
height={327}
ref={(ref) => {
this.ctx = ref.getContext('2d'); // <=== remove this
this.canvas = ref;
} }
/>
So rather than saving the reference to the getContext, I just cached the canvas element and called canvas.getContext('2d') when I needed to access the drawing functions.

Resources