React SVG style element on hover - javascript

I have an array of squares inside SVG-based React component and I need to apply custom style (fill color set to purple) to square under cursor on hover.
I tried that with both CSS :hover and onMouseOver events, neither did work.
Any clues are much appreciated
const { render } = ReactDOM,
rootNode = document.getElementById('root')
const Matrix = ({m, n}) => (
<svg
viewBox={`0 0 ${m*10+10} ${n*10+10}`} xmlns="http://www.w3.org/2000/svg"
>
<defs>
<rect
id="cell"
width="5"
height="5"
className="cell"
onMouseOver={({target}) => target.style.cssText="fill:purple"}
/>
</defs>
<g>
{
Array(n).fill().map((row, rowIdx) => (
<g key={rowIdx}>
{
Array(m).fill().map((col, colIdx) => (
<g key={colIdx}>
<use
x={5+colIdx*5}
y={5+rowIdx*5}
xlinkHref="#cell"
fill="lightgray"
stroke="white"
strokeWidth=".4"
/>
</g>
))
}
</g>
))
}
</g>
</svg>
)
render (
<Matrix m={10} n={10} />,
rootNode
)
#cell:hover {
fill: purple;
}
.cell:hover {
fill: purple;
}
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script></script><div id="root"></div>

If you have to color each square you must have to use your class .cell inside the <use /> of each cell:
<use
className="cell"
x={5+colIdx*5}
y={5+rowIdx*5}
xlinkHref="#cell"
fill="grey"
stroke="white"
strokeWidth=".4"
/>
And now you can use your css style:
.cell:hover {
fill: purple;
}
Snippet:
const { render } = ReactDOM,
rootNode = document.getElementById('root')
const Matrix = ({m, n}) => (
<svg
viewBox={`0 0 ${m*10+10} ${n*10+10}`} xmlns="http://www.w3.org/2000/svg"
>
<defs>
<rect
id="cell"
width="5"
height="5"
/>
</defs>
<g>
{
Array(n).fill().map((row, rowIdx) => (
<g key={rowIdx}>
{
Array(m).fill().map((col, colIdx) => (
<g key={colIdx}>
<use
className="cell"
x={5+colIdx*5}
y={5+rowIdx*5}
xlinkHref="#cell"
fill="grey"
stroke="white"
strokeWidth=".4"
/>
</g>
))
}
</g>
))
}
</g>
</svg>
)
render (
<Matrix m={10} n={10} />,
rootNode
)
.cell:hover {
fill: purple;
}
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script></script><div id="root"></div>

Related

Transform origin on SVG pattern not working on Firefox/Safari

I made a hexagonal grid with an SVG pattern.
To zoom in, I used transform's scale function, and to keep it scaling from the center I set transform-origin to the center of the page.
This is perfectly working on Chrome but not on Safari and Firefox.
How can I make it work on all browsers?
Here's what I made so far:
const pattern = document.getElementById('hexagons');
const center = {
x: window.innerWidth / 2,
y: window.innerHeight / 2
};
const transformOrigin = `${center.x}px ${center.y}px`;
let zoom = 1;
const formatPatternTransform = (zoom) => `rotate(90) scale(${zoom})`;
function animate() {
if (zoom > 3)
return;
zoom += .005;
pattern.setAttribute('patternTransform', formatPatternTransform(zoom));
requestAnimationFrame(animate);
}
pattern.setAttribute('transform-origin', transformOrigin);
animate();
* {
margin: 0;
padding: 0;
}
svg {
background: #0a0a0a;
height: 100vh;
width: 100vw;
}
<svg ref="svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%">
<defs>
<pattern
id="hexagons"
ref="pattern"
width="50"
height="43.4"
patternUnits="userSpaceOnUse"
patternTransform="rotate(90)"
x="50%"
y="50%"
>
<polygon
id="hex"
points="24.8,22 37.3,29.2 37.3,43.7 24.8,50.9 12.3,43.7 12.3,29.2"
fill="#0a0a0a"
stroke="#222"
/>
<use xlink:href="#hex" x="25" />
<use xlink:href="#hex" x="-25" />
<use xlink:href="#hex" x="12.5" y="-21.7" />
<use xlink:href="#hex" x="-12.5" y="-21.7" />
</pattern>
</defs>
<rect id="mosaic" width="100%" height="100%" fill="url(#hexagons)" />
</svg>
why not simply scaling the whole SVG:
* {
margin: 0;
padding: 0;
}
svg {
position:fixed;
top:0;
left:0;
width:100%;
height:100%;
background: #0a0a0a;
animation:zoom 3s ease-out forwards
}
#keyframes zoom {
to {
transform:scale(3);
}
}
<svg ref="svg" xmlns:xlink="http://www.w3.org/1999/xlink" >
<defs>
<pattern
id="hexagons"
ref="pattern"
width="50"
height="43.4"
patternUnits="userSpaceOnUse"
patternTransform="rotate(90)"
x="50%"
y="50%"
>
<polygon
id="hex"
points="24.8,22 37.3,29.2 37.3,43.7 24.8,50.9 12.3,43.7 12.3,29.2"
fill="#0a0a0a"
stroke="#222"
/>
<use xlink:href="#hex" x="25" />
<use xlink:href="#hex" x="-25" />
<use xlink:href="#hex" x="12.5" y="-21.7" />
<use xlink:href="#hex" x="-12.5" y="-21.7" />
</pattern>
</defs>
<rect id="mosaic" width="100%" height="100%" fill="url(#hexagons)" />
</svg>
To avoid the lag you can keep you JS code:
let s = document.querySelector('svg');
let zoom = 1;
function animate() {
if (zoom > 3)
return;
zoom += .005;
s.style.transform = "scale(" + zoom + ")";
requestAnimationFrame(animate);
}
animate();
* {
margin: 0;
padding: 0;
}
svg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #0a0a0a;
}
<svg ref="svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<pattern
id="hexagons"
ref="pattern"
width="50"
height="43.4"
patternUnits="userSpaceOnUse"
patternTransform="rotate(90)"
x="50%"
y="50%"
>
<polygon
id="hex"
points="24.8,22 37.3,29.2 37.3,43.7 24.8,50.9 12.3,43.7 12.3,29.2"
fill="#0a0a0a"
stroke="#222"
/>
<use xlink:href="#hex" x="25" />
<use xlink:href="#hex" x="-25" />
<use xlink:href="#hex" x="12.5" y="-21.7" />
<use xlink:href="#hex" x="-12.5" y="-21.7" />
</pattern>
</defs>
<rect id="mosaic" width="100%" height="100%" fill="url(#hexagons)" />
</svg>
You can easily zoom out too:
let s = document.querySelector('svg');
let zoom = 1;
function animate() {
if (zoom < 0.2)
return;
zoom -= .005;
s.style.transform = "scale(" + zoom + ")";
requestAnimationFrame(animate);
}
animate();
* {
margin: 0;
padding: 0;
}
svg {
position:fixed;
top:-300%;
left:-300%;
width:700%;
height:700%;
background: #0a0a0a;
}
<svg ref="svg" xmlns:xlink="http://www.w3.org/1999/xlink" >
<defs>
<pattern
id="hexagons"
ref="pattern"
width="50"
height="43.4"
patternUnits="userSpaceOnUse"
patternTransform="rotate(90)"
x="50%"
y="50%"
>
<polygon
id="hex"
points="24.8,22 37.3,29.2 37.3,43.7 24.8,50.9 12.3,43.7 12.3,29.2"
fill="#0a0a0a"
stroke="#222"
/>
<use xlink:href="#hex" x="25" />
<use xlink:href="#hex" x="-25" />
<use xlink:href="#hex" x="12.5" y="-21.7" />
<use xlink:href="#hex" x="-12.5" y="-21.7" />
</pattern>
</defs>
<rect id="mosaic" width="100%" height="100%" fill="url(#hexagons)" />
</svg>
This is an alternative solution. Instead of using transform-origin you can center the <rect id="mosaic" around the origin of the svg canvas. In this case you will need a viewBox attribute: viewBox="-250 -250 500 500" and preserveAspectRatio="xMidYMid slice"
"xMidYMid (the default) - Force uniform scaling.
Align the midpoint X value of the element's viewBox with the midpoint X value of the viewport.
Align the midpoint Y value of the element's viewBox with the midpoint Y value of the viewport."
"slice - Scale the graphic such that:
aspect ratio is preserved
the entire viewport is covered by the viewBox
the viewBox is scaled down as much as possible, while still meeting the other criteria"
And this is the rectangle filled with the pattern:
<rect id="mosaic" x="-250" y="-250" width="500" height="500" fill="url(#hexagons)" />
Next comes a working example:
const pattern = document.getElementById('hexagons');
const center = {
x: window.innerWidth / 2,
y: window.innerHeight / 2
};
//const transformOrigin = `${center.x}px ${center.y}px`;
let zoom = 1;
const formatPatternTransform = (zoom) => `rotate(90) scale(${zoom})`;
function animate() {
if (zoom > 3)
return;
zoom += .005;
pattern.setAttribute('patternTransform', formatPatternTransform(zoom));
requestAnimationFrame(animate);
}
//pattern.setAttribute('transform-origin', transformOrigin);
animate();
* {
margin: 0;
padding: 0;
}
svg {
background: #0a0a0a;
height: 100vh;
width: 100vw;
}
<svg ref="svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-250 -250 500 500" preserveAspectRatio="xMidYMid slice">
<defs>
<pattern
id="hexagons"
ref="pattern"
width="50"
height="43.4"
patternUnits="userSpaceOnUse"
patternTransform="rotate(90)"
x="50%"
y="50%"
>
<polygon
id="hex"
points="24.8,22 37.3,29.2 37.3,43.7 24.8,50.9 12.3,43.7 12.3,29.2"
fill="#0a0a0a"
stroke="#222"
/>
<use xlink:href="#hex" x="25" />
<use xlink:href="#hex" x="-25" />
<use xlink:href="#hex" x="12.5" y="-21.7" />
<use xlink:href="#hex" x="-12.5" y="-21.7" />
</pattern>
</defs>
<rect id="mosaic" x="-250" y="-250" width="500" height="500" fill="url(#hexagons)" />
</svg>

How to switch position of D3 SVG elements

I have some SVG elements on my page that I created with D3. All are children of one parent SVG. Each contains some other D3 elements like paths and text. On the click of a button, I want two of these child SVGs to switch positions, so they move up or down on the page (all are placed above/below each other).
I already tried creating groups ("g") instead of the child SVGs and accessing/changing their positions. However, I can't seem to access the y position of the element.
I also tried using "insertAfter" but this only works with divs, not with SVGs (however, I'm looking for a similar behaviour).
$(".move_up").click(function() {
var svg = $("#second_child"); //ID is actually retrieved from an attribute of the button
svg.insertBefore(svg.prev()); //does obviously not work
});
HTML for move up button (one per child SVG exists):
<a class="move_up">
<span class="grey glyphicon glyphicon-chevron-up" title="Move up"></span>
</a>
HTML for SVG:
<div>
<svg id="parent">
<svg id="first_child"
<path></path>
<rect></rect>
<text></text>
...
</svg>
<svg id="second_child"
<path></path>
<rect></rect>
<text></text>
...
</svg>
<rect></rect>
<text></text>
...
</svg>
</div>
I want the first and second child SVGs to switch positions, when the move up (or respectively a move down) button is used.
This is what I ended up doing:
let group = $("#first_group");
let next_group = $('#second_group");
let diff = 58; // Height of a group
let translate_y = 0;
let translate_y_next = 0;
if (next_group.offset()) {
if (group.attr("transform")) {
let string = group.attr("transform");
translate_y = parseInt(string.substring(string.indexOf("(")+1, string.indexOf(")")).split(",")[1]);
}
if (prev_group.attr("transform")) {
let string_next = prev_group.attr("transform");
translate_y_next = parseInt(string_next.substring(string_next.indexOf("(")+1, string_next.indexOf(")")).split(",")[1]);
}
group.attr("transform", `translate(0, ${translate_y + diff})`);
next_group.attr("transform", `translate(0, ${translate_y_next - diff})`);
}
Works similar for a "Move up" button. Just make sure to change the sign in the last two lines!
May not be super elegant, but does the job.
You are using an SVG as wrapper and the positions are different to html. In SVG You need to define the X and Y position.
let ids = ['ex1', 'ex2', 'ex3', 'ex4', 'ex5']
let btn = document.getElementById('move')
const sortArrayAsYouWish = (array) => {
array.sort(() => Math.random() - 0.5);
}
const changeOrder = () => {
let posY = 35
sortArrayAsYouWish(ids) // change order
ids.forEach((id, i) => {
let $el = document.getElementById(id)
$el.style.transform = `translate(0, ${posY*i}px)`
})
}
btn.onclick = changeOrder
changeOrder()
svg {
width: 500px;
height: 340px;
border: solid 1px #ccc;
}
g {
transition: transform 0.4s;
}
text {
fill: #fff;
text-anchor: middle;
}
#ex2 rect {
fill: blue;
}
#ex3 rect {
fill: yellow;
}
#ex4 rect {
fill: red;
}
#ex5 rect {
fill: cyan;
}
<div><button id="move">Move</button></div>
<svg>
<g id="ex1">
<rect width="120" height="30" x="0" y="0" />
<text x="60" y="15">Hello example 1</text>
</g>
<g id="ex2">
<rect width="120" height="30" x="0" y="0" />
<text x="60" y="15">Hello example 2</text>
</g>
<g id="ex3">
<rect width="120" height="30" x="0" y="0" />
<text x="60" y="15">Hello example 3</text>
</g>
<g id="ex4">
<rect width="120" height="30" x="0" y="0" />
<text x="60" y="15">Hello example 4</text>
</g>
<g id="ex5">
<rect width="120" height="30" x="0" y="0" />
<text x="60" y="15">Hello example 5</text>
</g>
</svg>

create a responsive svg with react

I want to create an svg that will be easily scaled on whatever width I'll give it. In that svg I have a circle and path with relative paths so I want it to be easily scaleable.
But for some reason the position of x,y are not right.
You can see the image below, the circle has (x,y)=(0,0)
but it is set in the middle.
I assume "viewBox" is causing problems.
jsfiddle link:
https://jsfiddle.net/tzookb/69z2wepo/174297/
code:
const ExclamationIcon = props => (
<svg {...props} viewBox="0 0 140 140">
<circle fill="#f66868" cx="70" cy="70" r="70" />
<g transform="translate(58,30)">
<path fill="white" d="M 11,56.7 L 9,56.7 L 4.2,13.2 C 4,11.4 4,10 4,9 C 4,7 4.5,4.9 5.6,3.5 C 7,2.2 8.3,1.5 10,1.5 C 11.7,1.5 13,2.2 14.4,3.5 C 15.5,4.9 16,7 16,9 C 16,10 16,11.4 15.8,13.2 z M 10,64 A 6,6 0 1,1 10,76 A 6,6 0 1,1 10,64 z"/>
</g>
</svg>
);
class Hello extends React.Component {
render() {
return (
<div>
asdas
<svg className="main">
<rect x="20" y="20" width="100" height="40" fill="red" />
<ExclamationIcon width="30" x="0" y="0" />
</svg>
</div>
);
}
}
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
Looks like this issue goes away if the height is set on the inner svg. I'm guessing that if the height is not set it automatically takes up all the available height and centres itself. I wish I had a better explanation but this completely stumped me.
JS Fiddle
const ExclamationIcon = props => (
<svg {...props} viewBox="0 0 140 140">
<circle fill="#f66868" cx="70" cy="70" r="70" />
<g transform="translate(58,30)">
<path fill="white" d="M 11,56.7 L 9,56.7 L 4.2,13.2 C 4,11.4 4,10 4,9 C 4,7 4.5,4.9 5.6,3.5 C 7,2.2 8.3,1.5 10,1.5 C 11.7,1.5 13,2.2 14.4,3.5 C 15.5,4.9 16,7 16,9 C 16,10 16,11.4 15.8,13.2 z M 10,64 A 6,6 0 1,1 10,76 A 6,6 0 1,1 10,64 z"/>
</g>
</svg>
);
class Hello extends React.Component {
render() {
return (
<div>
asdas
<svg className="main">
<rect x="20" y="20" width="100" height="40" fill="red" />
<ExclamationIcon width="30" height="30" x="0" y="0" />
</svg>
</div>
);
}
}
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);

SVG Knockout text styling issue: unexplained cropped bottom

I've tried to build a react component for knockout text and I run into a styling issue I can't explain to myself.
Here is my attempt.
const styles = {
container: {
backgroundSize: "cover",
backgroundImage: "url(http://brokensquare.com/Code/assets/landscape.jpg)",
padding: "20% 20%"
},
knockout: {
borderRadius: 200,
overflow: "hidden"
}
};
const Knockout = ({ text, style }) => {
const s = style || {};
return (
<div style={styles.knockout}>
<svg viewBox="0 0 200 25">
<rect
fill={s.backgroundColor || "rgba(0,0,0,0.6)"}
x="0"
y="0"
width="100%"
height="100%"
mask="url(#knockout-text)"
/>
<mask id="knockout-text">
<rect fill="#fff" x="0" y="0" width="100%" height="100%" />
<text y="70%" fill="#000" textAnchor="middle" x="50%">
{text}
</text>
</mask>
</svg>
</div>
);
};
const App = Radium(() => (
<div>
<div style={styles.container}>
<Knockout style={{}} text={"VERY INSPIRATION"} />
</div>
</div>
));
As you can see the bottom is cropped for some reason, instead of having the side completely rounded, drawing half a circle. Can anyone here see why and how I could fix this ? Thanks.
add this css, it will work
svg {
display: block
}
you can check below codesandbox
https://codesandbox.io/s/71qxyx6m86
I added styles as below
render(
<div>
<App />
<Style
rules={{
"*": {
margin: 0,
padding: 0,
boxSizing: "border-box"
},
svg: {
display: "block"
}
}}
/>
</div>,
document.getElementById("root")

svg not rendering correctly when used with by multiple react components

Context
I have an SVG that is used by two different React components (A and B) on the same page.
Problem
When component A is assigned 'display: none' css property, the svg in component B does not render correctly.
Example
componentA {
display: none;
}
componentB {
display: block;
}
SVG does not render correctly
componentA {
display: block;
}
componentB {
display: block;
}
SVG renders correctly
I suspect it may be an issue with my svg but I am not sure as I am new to react. Below is the svg code.
<svg
id={id}
data-name={id}
xmlns="http://www.w3.org/2000/svg"
width={width}
height={height}
viewBox="0 0 498 305.84">
<defs>
<clipPath id="clip-path">
<path
fill="none"
d="M354.47 137.84c1.75-22.18-9.65-52.24-34-73.15-15.94 51.41 30.59 56.14 34 73.15" />
</clipPath>
<linearGradient
id="linear-gradient"
x1="-146.72"
y1="416.08"
x2="-143.8"
y2="416.08"
gradientTransform="matrix(7.8 0 0 -7.8 1469.73 3349.9)"
gradientUnits="userSpaceOnUse">
<stop offset="0%" stopColor="#62bb46" />
<stop offset="100%" stopColor="#a2d28a" />
</linearGradient>
</defs>
<g id="Leaf">
<path
clipPath="url(#clip-path)"
fill="url(#linear-gradient)"
d="M286.058 69.72l66.622-18.076 22.032 81.205-66.622 18.074z" />
<path
fill="#62bb46"
d="M320.48 64.69c23.64 2.71 53.94 33.25 34 73.15 1.76-22.18-9.65-52.24-34-73.15" />
</g>
</svg>
I had a simple workaround that worked for me at least.
The only change I would do is append the unique id to each of the elements with an id attribute,
<svg
id={id}
data-name={id}
...
<defs>
<clipPath id={`clip-path-${id}`}>
...
</clipPath>
<linearGradient
id={`linear-gradient-${id}`}
...
</linearGradient>
</defs>
<g id={`Leaf-${id}`}>
<path
clipPath={`url(#clip-path-${id})`}
fill={`url(#linear-gradient-${id})`}
...
/>
</g>
</svg>
By doing so, it is always assured that SVG will have unique id on different components.
As of now there is a feature request in svgo to uniquely allocate id for each svg instance.
Best work around could be not to use clip-path in svg
Another way can be to use following css to hide element
componentA {
display: block;
visibility: hidden;
height: 0;
width: 0;
}
componentB {
display: block;
}
Another work around can be to pass unique id as a porp and replacing it with id required for clip-path as answered earlier.
e.g
export const svg = (id) => <svg xmlns="http://www.w3.org/2000/svg" width={width} height={height} viewBox="0 0 498 305.84">
<defs>
<clipPath id={`clip-path-&{id}`}>
<path
fill="none"
d="M354.47 137.84c1.75-22.18-9.65-52.24-34-73.15-15.94 51.41 30.59 56.14 34 73.15" />
</clipPath>
<linearGradient
id={`linear-gradient-${id}`}
x1="-146.72"
y1="416.08"
x2="-143.8"
y2="416.08"
gradientTransform="matrix(7.8 0 0 -7.8 1469.73 3349.9)"
gradientUnits="userSpaceOnUse">
<stop offset="0%" stopColor="#62bb46" />
<stop offset="100%" stopColor="#a2d28a" />
</linearGradient>
</defs>
<g id="Leaf">
<path
clipPath={`url(#clip-path-&{id})`}
fill={`url(#linear-gradient-${id})`}
d="M286.058 69.72l66.622-18.076 22.032 81.205-66.622 18.074z" />
<path
fill="#62bb46"
d="M320.48 64.69c23.64 2.71 53.94 33.25 34 73.15 1.76-22.18-9.65-52.24-34-73.15" />
</g>
</svg>

Categories

Resources