Making a clickable SVG that doesn't trigger a mouseLeave

Viewed 139

What I'm trying to do:

  • when you hover over the row, color changes and a SVG appears
  • you can click the SVG (currently displays 'clicked' in console) to trigger a function (needs to support triggering external functions too, not just ones that need to be defined inside script tags inside svg, so props functions for example)
  • when your mouse leaves, the row reverts to original form

Problems:

  • hovering over the SVG button triggers a mouseLeave (leaves the row) if pointer-events is not set to none. If it is set to none, the SVG button becomes unclickable.
  • onClick inside SVG doesn't seem to support external functions (props functions for example)

Would appreciate help with this.

const { Container, Row, Col} = ReactBootstrap;
function Library(props){
  
  function handleMouseEnter(e){
    e.style.background = 'blue';
    var targetNode = e.querySelector(`#content`);
    ReactDOM.render(                
      <svg  width="0.75rem" height="0.75rem" className="Layer_1" x="0px" y="0px" viewBox="0 0 472.615 472.615" fill="white">   
        <polygon onClick={e => console.log('clicked')} points="50.273,0 50.273,472.615 422.342,236.308"/>
      </svg>
      , targetNode);
  }
  
  function handleMouseLeave(e){
    e.style.background = '';
    var targetNode = e.querySelector(`#content`);
    ReactDOM.render(                
      "hover over me and click the button!"
      , targetNode);
  }
  
  return(
  <Container>
    <Row 
    onMouseEnter={e => handleMouseEnter(e.target.closest('.row'))}
    onMouseLeave={e => handleMouseLeave(e.target.closest('.row'))}
    >
      <Col>
        <div id="content">
          hover over me and click the button!
        </div>        
      </Col>
    </Row>
  </Container>
  )
}

ReactDOM.render(
  <Library/>,
  document.getElementById('root')
);
.row{
background:red;
}
svg{
pointer-events:none;
}
<script src="https://unpkg.com/react/umd/react.production.min.js" crossorigin></script>

<script
  src="https://unpkg.com/react-dom/umd/react-dom.production.min.js"
  crossorigin></script>

<script
  src="https://unpkg.com/react-bootstrap@next/dist/react-bootstrap.min.js"
  crossorigin></script>
<div id="root"></div>

3 Answers

You can create file .svg and store your svg on that file. example

Icon.svg

    <svg  width="0.75rem" height="0.75rem" className="Layer_1" x="0px" y="0px" viewBox="0 0 472.615 472.615" fill="white">   
        <polygon points="50.273,0 50.273,472.615 422.342,236.308"/>
    </svg>

And you can render your Icon.svg into your component example:

YourComponent.jsx

import { ReactComponent as Icon } from "./Icon.svg";

const YourComponent = () => {
  return (
    <div>
      <Icon onClick={e => console.log('clicked')} />
    </div>
  );
};

export default YourComponent;

and congratulation now you can click your Icon.svg.

in best practice if you wanna import some .svg images you should import as ReactComponent

Maybe you can try this using useState for conditional rendering

import { useState } from "react";
import { ReactComponent as Icon } from "./Icon.svg";

const YourApp = () => {
  const [onHover, setOnHover] = useState(false);

  const handleMouseEnter = () => {
    setOnHover(true);
  };
  const handleMouseLeave = () => {
    setOnHover(false);
  };

  return (
    <div
      style={onHover ? { background: "blue" } : { background: "red" }}
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
    >
      {onHover && <Icon onClick={(e) => console.log("clicked")} />}
      hover over me and click the button!
    </div>
  );
};

export default YourApp;

I think I fixed the problem by editing onMouseLeave to check if the element that triggered it was a SVG or one of it's children, if it is, then count it as a false positive. Now the button is clickable and doesn't disappear if you hover over it.

const { Container, Row, Col} = ReactBootstrap;
function Library(props){
  
  function handleMouseEnter(row){
    row.style.background = 'blue';
    var targetNode = row.querySelector(`#content`);
    ReactDOM.render(                
      <svg onClick={e => console.log('clicked')} width="0.75rem" height="0.75rem" className="Layer_1" x="0px" y="0px" viewBox="0 0 472.615 472.615" fill="white">   
        <polygon points="50.273,0 50.273,472.615 422.342,236.308"/>
      </svg>
      , targetNode);
  }
  
  function handleMouseLeave(e, row){
    if(e.relatedTarget.tagName === 'svg' || e.relatedTarget.tagName === 'polygon') return
    row.style.background = '';
    var targetNode = row.querySelector(`#content`);
    ReactDOM.render(                
      "hover over me and click the button!"
      , targetNode);
  }
  
  return(
  <Container>
    <Row 
    onMouseEnter={e => handleMouseEnter(e.target.closest('.row'))}
    onMouseLeave={e => handleMouseLeave(e, e.target.closest('.row'))}
    >
      <Col>
        <div id="content">
          hover over me and click the button!
        </div>        
      </Col>
    </Row>
  </Container>
  )
}

ReactDOM.render(
  <Library/>,
  document.getElementById('root')
);
.row{
background:red;
}
<script src="https://unpkg.com/react/umd/react.production.min.js" crossorigin></script>

<script
  src="https://unpkg.com/react-dom/umd/react-dom.production.min.js"
  crossorigin></script>

<script
  src="https://unpkg.com/react-bootstrap@next/dist/react-bootstrap.min.js"
  crossorigin></script>
<div id="root"></div>

Related