Hide transparent image from download while using React Konva - javascript

I want to download only the papayawhip square box without the transparent image behind it.
I am using React Konva. I have the following code:
import * as React from "react"
import { Stage, Layer, Rect } from "react-konva"
import type { Stage as StageType } from "konva/types/Stage"
import { observer } from "mobx-react"
import "./styles.css"
import { useStore } from "./context"
const downloadURI = (uri: string | undefined, name: string | undefined) => {
const link = document.createElement("a")
link.download = name as string
link.href = uri as string
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
function App() {
const [fillColor, setFillColor] = React.useState("")
const [downloadClicked, setDownloadClicked] = React.useState(false)
const stageRef = React.useRef<StageType>(null)
const transparentBackground = new window.Image()
transparentBackground.src =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAABlBMVEUAAADY2NjnFMi2AAAAAXRSTlMAQObYZgAAABVJREFUGNNjYIQDBgQY0oLDxBsIQQCltADJNa/7sQAAAABJRU5ErkJggg=="
const store = useStore()
const { win, canvas, browser, pad } = store
return (
<div className="flex">
<div id="sidebar">
<h1 className="text">
Center <span>papayawhip</span> Canvas
</h1>
<input
type="text"
placeholder="Enter Fill Color"
value={fillColor}
onChange={(e) => setFillColor(e.target.value)}
/>
<button
className="inline-flex items-center px-4 py-2 text-sm font-medium text-white bg-indigo-600 border border-transparent rounded-md shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
onClick={() => {
setDownloadClicked(true)
const options = { mimeType: `image/png`, quality: 1, pixelRatio: 1 }
const img = stageRef.current?.getStage().toDataURL(options)
downloadURI(img, "download.png")
setDownloadClicked(false)
}}
>
Download Image
</button>
</div>
<Stage
ref={stageRef}
width={canvas.width}
height={canvas.height}
id="konva"
>
<Layer>
{fillColor === "" && downloadClicked && (
<Rect
width={browser.width + 200}
height={browser.height + 200}
x={pad / 2}
y={(win.height - browser.height) / 2}
fillPatternImage={transparentBackground}
fill={fillColor}
/>
)}
<Rect
width={browser.width}
height={browser.height}
x={pad / 2}
y={(win.height - browser.height) / 2}
fill="papayawhip"
/>
</Layer>
</Stage>
</div>
)
}
export default observer(App)
Codesandbox → https://codesandbox.io/s/add-padding-to-centered-canvas-with-sidebar-gqhhl?file=/src/App.tsx
How do I download only the papayawhip rectangle part?

In principle,
hide any shapes you don't want included in the image,
Get the image
unhide what you hid in 1.
Note: do not draw the layer/stage between 1 and 3 or you will see an unwanted flicker.

You can pass x, y, width and height attributes to stage.toDataURL(options) function to capture only a part of the screen:
const options = {
mimeType: `image/png`,
quality: 1,
pixelRatio: 1,
width: browser.width,
height: browser.height,
x: pad / 2,
y: (win.height - browser.height) / 2
};
const img = stageRef.current?.getStage().toDataURL(options);
https://codesandbox.io/s/react-konva-export-part-of-the-stage-hf5sn

I solved it with the help of Anton. Vanquished Wombat's approach was the same as well.
const backgroundSelector = React.useRef(null)
/* gave a `name` to `transparentBackground` rectangle */
<Rect
name="transparentBackground"
/>
/* then searched for it & hid it */
stageRef.current?.findOne(".transparentBackground").hide()
/* download the image */
downloadURI(img, "download.png")
/* show it again */
stageRef.current?.findOne(".transparentBackground").show()
Updated Codesandbox → https://codesandbox.io/s/add-padding-to-centered-canvas-with-sidebar-gqhhl?file=/src/App.tsx

Related

How to Dynamically add nodes in the graph using react-sigma

I'm new to Sigma and I'm trying to dynamically add a node to the graph but I'm receiving an error
react-dom.development.js:22839 Uncaught Error: Sigma: invalid graph instance.
The above error occurred in the <ForwardRef> component:
I want to create a simple react Sigma application where users can dynamically add nodes to the graph. In Chrome, I'm able to successfully add a single node, but the moment I update my input tag it breaks and prints the error messages shown above.
here below is my code
import React, { useState } from "react";
import Graph from "graphology";
import { SigmaContainer } from "#react-sigma/core";
const GraphPage = () => {
const [graph, setGraph] = useState(new Graph());
var i = 0;
const [node, setNode] = useState("");
const addNode = (e) => {
e.preventDefault();
console.log(
"just want to see what the e.target.node_name.value is : " +
e.target.node_name.value
);
const nodeName = e.target.node_name.value;
setGraph(
graph.addNode(nodeName, {
x: i,
y: i,
label: nodeName,
size: 15,
color: "#FA4F40",
})
);
setGraph(
graph.addNode(nodeName + i, {
x: i + 1,
y: i + 1,
label: nodeName + i,
size: 15,
color: "#FA4F40",
})
);
setGraph(
graph.addEdge(nodeName, nodeName + i, {
type: "arrow",
label: "works with",
size: 5,
})
);
i++;
};
return (
<div>
<h1>Graph</h1>
<h2>Add Node</h2>
<form onSubmit={addNode}>
<label
htmlFor="first_name"
className="block mb-2 text-sm font-medium text-gray-900 "
>
Node name
</label>
<input
type="text"
id="node_name"
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
// value={name}
// onChange={handleChange}
required
/>
<button className="btn-start px-8">Submit</button>
</form>
<SigmaContainer
style={{ height: "1000px" }}
graph={graph}
></SigmaContainer>
</div>
);
};
export default GraphPage;
My guess is somewhere sigma doesn't like to rerender the graph. Not sure how to go about it, and I don't see any examples of anyone dynamically adding nodes. Any help would be appreciated. Thank you

Problem while trying to update style of a Jsx element

import React, { Component } from 'react'
class ProgressBar extends Component {
render() {
let progressContainer = document.querySelector('.progress-container');
let valueContainer = document.querySelector('.progress-value');
const speed = 20;
let progressValue = 0;
let progressEndValue = 70;
function updateElements() {
valueContainer = document.querySelector('.progress-value');
progressContainer = document.querySelector('.progress-container');
}
const createProgress = setInterval(() => {
progressValue++;
updateElements();
valueContainer.innerText = `${progressValue} %`
progressContainer.style.background = `conic-gradient(
rgb(239 68 68) ${progressValue * 3.6}deg,
black 1deg,
rgb(241 245 249) 1deg,
)`
if (progressValue == progressEndValue) {
clearInterval(createProgress);
}
}, speed)
return (
<div className='progress progress-container w-full h-full rounded-full flex justify-center items-center'>
<div className="progress w-3/4 h-3/4 rounded-full bg-slate-100 flex justify-center items-center">
<h1 className='progress-value' >0 %</h1>
</div>
</div>
)
}
}
export default ProgressBar;
So here is my code, I am basically trying to create a dynamic animated circular progress bar here.
I use updateElements function to prevent the uncaught error of null, the progress value is changing between 0 and 70 percent successfully in the DOM. but the conic-gradient background does not applying in the DOM from the function. but if I set it statically in the CSS file with the same code. it works.
Someone help me please I am struggling since yesterday!!
import React, { Component } from 'react'
class ProgressBar extends Component {
state={
progressValue:0,
speed:20,
progressEndValue:70
}
render() {
let progressContainer = document.querySelector('.progress-container');
let valueContainer = document.querySelector('.progress-value');
function helperFunctions() {
valueContainer = document.querySelector('.progress-value');
progressContainer = document.querySelector('.progress-container');
}
const createProgress = setInterval(() => {
if (this.state.progressValue <= this.state.progressEndValue) {
this.setState({progressValue:this.state.progressValue+1});
helperFunctions();
valueContainer.innerText = `${this.state.progressValue} %`
progressContainer.style.background = `conic-gradient(rgb(239 68 68) ${this.state.progressValue * 3.6}deg,black 1deg,rgb(241 245 249) 1deg)`
} else {
clearInterval(createProgress);
}
}, this.state.speed)
return (
<div className='progress progress-container w-full h-full rounded-full flex justify-center items-center'>
<div className="progress w-3/4 h-3/4 rounded-full bg-slate-100 flex justify-center items-center">
<h1 className='progress-value' >0 %</h1>
</div>
</div>
)
}
}
export default ProgressBar;
Now it works fine :)
suggestion:
If your were using functional component, it could be done much easier and you could use useRef intead of document.querySelector as it is recomanded is React document
The main problem was the last , in conic-gradient

How to change the slider step from 1% to 0.5% on Range Slider? - Javascript - React

After many unsuccessful attempts, I wish to get help on how to reduce the step size of the range slider from 1% to 0.5% .
The shoe sizes values are between 34 and 50. I would like to get values like 34.5, 35.5 etc.. now while moving the range slider i get values like 34 - 35 - 36 etc...
This is the code:
import React, { useCallback, useEffect, useState, useRef } from "react";
import classnames from "classnames";
import PropTypes from "prop-types";
import { FilterIcon } from "#heroicons/react/solid";
const MultiRangeShoeSizeSlider = ({ min, max, onChange }) => {
const [minVal, setMinVal] = useState(min);
const [maxVal, setMaxVal] = useState(max);
const minValRef = useRef(null);
const maxValRef = useRef(null);
const range = useRef(null);
// Convert to percentage
const getPercent = useCallback(
(value) => Math.round(((value - min) / (max - min)) * 100),
[min, max]
);
// Set width of the range to decrease from the left side
useEffect(() => {
if (maxValRef.current) {
const minPercent = getPercent(minVal);
const maxPercent = getPercent(+maxValRef.current.value); // Preceding with '+' converts the value from type string to type number
if (range.current) {
range.current.style.left = `${minPercent}%`;
range.current.style.width = `${maxPercent - minPercent}%`;
}
}
}, [minVal, getPercent]);
// Set width of the range to decrease from the right side
useEffect(() => {
if (minValRef.current) {
const minPercent = getPercent(+minValRef.current.value);
const maxPercent = getPercent(maxVal);
if (range.current) {
range.current.style.width = `${maxPercent - minPercent}%`;
}
}
}, [maxVal, getPercent]);
// Get min and max values when their state changes
useEffect(() => {
onChange({ min: minVal, max: maxVal });
}, [minVal, maxVal, onChange]);
return (
<div className="flex flex-col">
<div className="flex flex-row">
{" "}
<FilterIcon
className="ml-1 mr-2 h-6 w-6 text-gray-400"
aria-hidden="true"
/>
<div className="text-sm text-slate-800 font-semibold mb-3">
Shoe Size (EU)
</div>
</div>
<div className="container">
<input
type="range"
min={min}
max={max}
value={minVal}
ref={minValRef}
onChange={(event) => {
const value = Math.min(+event.target.value, maxVal - 0.5);
setMinVal(value);
event.target.value = value.toString();
}}
className={classnames("thumb thumb--zindex-3", {
"thumb--zindex-5": minVal > max - 100,
})}
/>
<input
type="range"
min={min}
max={max}
value={maxVal}
ref={maxValRef}
onChange={(event) => {
const value = Math.max(+event.target.value, minVal + 0.5);
setMaxVal(value);
event.target.value = value.toString();
}}
className="thumb thumb--zindex-4"
/>
<div className="slider">
<div className="slider__track" />
<div ref={range} className="slider__range" />
<div className="slider__left-value">{minVal}</div>
<div className="slider__right-value">{maxVal}</div>
</div>
</div>
</div>
);
};
MultiRangeShoeSizeSlider.propTypes = {
min: PropTypes.number.isRequired,
max: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired,
};
export default MultiRangeShoeSizeSlider;
I tried dividing by the 2 the min and max percent values...
Please let me know if you also need the css file to test it out.

Need help making a switch selector compnent in reactJS similar to that on https://www.themoviedb.org/

So I'm trying to make a dynamic reusable switch selector component exactly like that on https://www.themoviedb.org/ to select between a number of options such as ["a", "b", "c"].
I've got most of the logic down, it might be a bit messy now, but my problem really is that I can't seem to figure out at which width or distance to move the coloured div to accurately place it right on top of the label/option title.
This is what I've got so far, the text colour changes correctly when selected, and the transaction is also smooth, but the position is wrong.
type SwitchProps = {
optionTitles: string[];
};
type Selector = {
isToggled: boolean;
optionTitle: string;
width: number | undefined;
};
const Switch: FC<SwitchProps> = (props) => {
const [selectors, setSelectors] = useState<Selector[]>([]);
const [currentToggled, setCurrentToggled] = useState<Selector & { index: number }>({
index: 0,
isToggled: true,
optionTitle: props.optionTitles[0],
width: 110,
});
const elementsRef = useRef<RefObject<HTMLDivElement>[]>(props.optionTitles.map(() => createRef()));
useLayoutEffect(() => {
if (selectors.length >= props.optionTitles.length) {
return;
}
props.optionTitles.map((optionName, index) => {
setSelectors((prevState) => [
...prevState,
{
isToggled: index === 0 ? true : false,
optionTitle: optionName,
width: 110,
},
]);
});
}, []);
const handlerToggleClick = (sectorIndex: number, toggleState: boolean) => {
let data = selectors;
data.forEach((selector, index) => {
selector.isToggled = false;
selector.width = elementsRef.current[sectorIndex].current?.offsetWidth;
});
data[sectorIndex].isToggled = true;
setCurrentToggled({ index: sectorIndex, ...data[sectorIndex] });
setSelectors(data);
};
return (
<div className="relative z-[1] h-8 border border-solid border-tmdbDarkBlue rounded-[30px] font-medium flex items-center">
{selectors.map((sector, index) => (
<div
key={index}
ref={elementsRef.current[index]}
className={`py-1 px-5 h-8 text-sm font-semibold flex items-center ${
sector.isToggled && "switch-active-text"
}`}
>
<span
className="cursor-pointer flex items-center"
onClick={() => handlerToggleClick(index, !sector.isToggled)}
>
{sector.optionTitle}
</span>
</div>
))}
<div
className={`absolute z-[-1] h-8 w-20 bg-tmdbDarkBlue rounded-[30px] transition-all duration-150 ease-in`}
style={{
width: currentToggled.width,
left: currentToggled.index === 0 ? 0 : (currentToggled.width as number) * 1.8,
}}
></div>
</div>
);
};
export default Switch;
If there are other ways to improve my code, please do let me know. I'm trying to get better at things, which is why I'm working on this clone sorta project.

Dynamically create object literals based on prop title and color

I am working on my Next.js website. Using Tailwind CSS I have managed to change the color dynamically before returning a component with static names & colors.
Now I am fetching data from the API and the title + color needs to be dynamically created with the values from the API.
the title prop equals the hardcoded project (1,2,3) name. The color should be color prop.
Is there an intelligent way to create an object literal with the dynamic data?
Hardcoded values work just perfectly fine.
import { motion } from 'framer-motion';
import Image from 'next/image';
import Link from 'next/link';
import { projectsArrayProps } from '#typings/propTypes';
const Projects = ({ allProjects }) => {
console.log(allProjects);
return (
<div className="card--grid grid grid-cols-3 lg:grid-cols-4 gap-4 auto-rows-[100px] sm:auto-rows-[120px] md:auto-rows-[200px]">
{allProjects.map(
({ id, logo, title, color }: projectsArrayProps, index) => {
// const projectColor: { [key: string]: string } = {
// project1: 'bg-[#fbc340]/[0.07]',
// project2: 'bg-[#70d1db]/[0.07]',
// project3: 'bg-[#ea5b52]/[0.07]',
// project4: 'bg-[#ff1e00]/[0.07]',
// project5: 'bg-[#ff99f3]/[0.07]',
// };
const projectColor: { [key: string]: string } = {};
return (
<motion.div
key={id}
className={`rounded-md bg-[${projectColor['title']}]`}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
transition={{
duration: 0.1,
stiffness: 200,
delay: index * 0.085,
type: 'spring'
}}
variants={{
hidden: { opacity: 0, scale: 0.6 },
visible: { opacity: 1, scale: 1 }
}}
>
<Link href="">
<a
target="_blank"
className="flex flex-col items-center justify-center w-full h-full"
>
<div className="flex flex-col items-center justify-center relative w-full h-full max-w-[64px] md:max-w-[100px] lg:max-w-[120px]">
<img
className="object-contain"
src={logo.url}
alt={logo.alt}
/>
</div>
</a>
</Link>
</motion.div>
);
}
)}
</div>
);
};
export default Projects;
Tailwind extracts classes at build time, so you cannot create dynamic classes at runtime. You could safelist some classes to make sure they are created even when they are not present in the code at build time (https://tailwindcss.com/docs/content-configuration#safelisting-classes). Unfortunately this only helps you if you know all possible colors that can be returned from the API.

Categories