最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Why a click on svg element is not captured by node contains method? - Stack Overflow

programmeradmin0浏览0评论

I have a basic modal with a svg icon within its html structure. I want to detect clicks outside the modal so I can close it. The code to do so is something like the following:

document.addEventListener('mousedown', (e) => {
  if(modal.contains(e.target)) return;
  closeModal();
});

However, when I click on the svg (which inside the modal), the click event is considered to be outside the modal and I can't figure out why. To have it work as I want, I have to do pointer-events: none to the svg element.

The simplified html structure looks like this:

  <div class="modal">
    <div class="css-t7awl3">
      <form>
        <div class="css-1incodr">
          <label class="css-g2pzhe" for="previousPassword">
            Mot de passe actuel<span class="css-5fl39m">*</span>
          </label>
        <div class="css-1xm32e0">
          <input
            class="css-17lan49"
            type="text"
            placeholder="**********"
            name="previousPassword"
          />
            <div id="test-input-toggle-password-visibility" class="css-1vw18kh">
              <svg
                height="16"
                viewBox="0 0 32 32"
                fill="none"
                xmlns=";
              />
            </div>
          </div>
        </div>
        <button class="css-1pnrrva" disabled="">
          Valider
        </button>
      </form>
    </div>
  </div>

Edit: Maybe it can help if I paste the actual react code here, as I am not sure if it's and issue with svg elements rather than my code.

    export const PasswordInput = React.forwardRef<HTMLInputElement, PasswordInputProps>(({
  placeholder,
  name,
  id,
  disabled,
  className,
}: PasswordInputProps, ref) => {
  const [showPassword, setShowPassword] = useState(false);

  const DisplayedPicto = showPassword ? ClosedEyePicto : EyePicto;

  return (
    <Container className={className}>
      <CustomInput
        className={className}
        type={showPassword ? 'text' : 'password'}
        placeholder={placeholder}
        name={name}
        disabled={disabled}
        ref={ref}
        id={id}
      />
      <StyledSuffix
        onMouseLeave={() => setShowPassword(false)}
        onMouseDown={() => setShowPassword(true)}
        onMouseUp={() => setShowPassword(false)}
        id='test-input-toggle-password-visibility'
      >
        <DisplayedPicto height={16} />  //svg element
      </StyledSuffix>
    </Container>
  );
});

I have a basic modal with a svg icon within its html structure. I want to detect clicks outside the modal so I can close it. The code to do so is something like the following:

document.addEventListener('mousedown', (e) => {
  if(modal.contains(e.target)) return;
  closeModal();
});

However, when I click on the svg (which inside the modal), the click event is considered to be outside the modal and I can't figure out why. To have it work as I want, I have to do pointer-events: none to the svg element.

The simplified html structure looks like this:

  <div class="modal">
    <div class="css-t7awl3">
      <form>
        <div class="css-1incodr">
          <label class="css-g2pzhe" for="previousPassword">
            Mot de passe actuel<span class="css-5fl39m">*</span>
          </label>
        <div class="css-1xm32e0">
          <input
            class="css-17lan49"
            type="text"
            placeholder="**********"
            name="previousPassword"
          />
            <div id="test-input-toggle-password-visibility" class="css-1vw18kh">
              <svg
                height="16"
                viewBox="0 0 32 32"
                fill="none"
                xmlns="http://www.w3/2000/svg"
              />
            </div>
          </div>
        </div>
        <button class="css-1pnrrva" disabled="">
          Valider
        </button>
      </form>
    </div>
  </div>

Edit: Maybe it can help if I paste the actual react code here, as I am not sure if it's and issue with svg elements rather than my code.

    export const PasswordInput = React.forwardRef<HTMLInputElement, PasswordInputProps>(({
  placeholder,
  name,
  id,
  disabled,
  className,
}: PasswordInputProps, ref) => {
  const [showPassword, setShowPassword] = useState(false);

  const DisplayedPicto = showPassword ? ClosedEyePicto : EyePicto;

  return (
    <Container className={className}>
      <CustomInput
        className={className}
        type={showPassword ? 'text' : 'password'}
        placeholder={placeholder}
        name={name}
        disabled={disabled}
        ref={ref}
        id={id}
      />
      <StyledSuffix
        onMouseLeave={() => setShowPassword(false)}
        onMouseDown={() => setShowPassword(true)}
        onMouseUp={() => setShowPassword(false)}
        id='test-input-toggle-password-visibility'
      >
        <DisplayedPicto height={16} />  //svg element
      </StyledSuffix>
    </Container>
  );
});
Share Improve this question edited Mar 23, 2021 at 15:38 Faris Marouane asked Mar 22, 2021 at 10:03 Faris MarouaneFaris Marouane 3421 gold badge4 silver badges18 bronze badges 9
  • 1 use pointer-events="all" for the svg element. – enxaneta Commented Mar 22, 2021 at 10:20
  • Thanks @enxaneta, but I don't want to use a trick (I already made it work with pointer-events: none). I want to know why it doesn't work. – Faris Marouane Commented Mar 22, 2021 at 10:41
  • 2 If you used pointer-events:none then the click won't register on that element – charlietfl Commented Mar 22, 2021 at 11:18
  • 1 empty svg elements or shapes with no fill do not respond to the mouse. You either need a fill or use `pointer-events="all" – enxaneta Commented Mar 22, 2021 at 11:20
  • 1 Can't reproduce this. See this example, clicks on the SVG are registered as "inside the modal": jsfiddle/35hzng89 Which browser do you use? – Sphinxxx Commented Mar 22, 2021 at 15:35
 |  Show 4 more ments

3 Answers 3

Reset to default 4

I just understood what was going on, and it has nothing to do with svgs. What's happening is that when I click on the svg, first a 'mousedown' event is triggered. Remember that a 'click' event consists of 2 events following each other: first a 'mousedown' and then a 'mouseup'.

So with the 'mousedown' event on the svg, the onMouseDown event handler, on the ponent StyledSuffix surrounding my svg, is triggered. That leads to the svg on the dom actually changing from EyePicto to ClosedEyPicto (const DisplayedPicto = showPassword ? ClosedEyePicto : EyePicto;). Therefore, the modal no longer 'contains' the original svg since it has changed!

A solution is to listen to the click with the event 'click' rather than with 'mousedown'. Because the orginal svg (EyePicto) will only register the 'mousedown' event and not then 'mouseup' because it disappeared from the dom between the 2.

I had this same problem, despite using a click event as OP suggested. The solution that finally worked for me was to apply pointer-events: none to the svg element inside the button, so that the SVG was never returned as the event target.

The Node.contains() method might not behave as expected when dealing with SVG elements; the reason being that SVG elements and HTML elements are part of two different namespaces in the DOM. SVG elements are typically in the "http://www.w3/2000/svg" namespace, while HTML elements are in the "http://www.w3/1999/xhtml" namespace.

When you use Node.contains(), it operates within the same namespace as the parent element. If your dropdown's content includes both HTML and SVG elements, you may encounter issues when trying to check containment between these different namespaces.

As a solution, a more lengthy but reliable method than using contains() is to use getBoundingClientRect() to find the position of the containing element in the viewport, and pare this to the click event's clientX/clientY properties. Example:

const handleOutsideClick = (event) => {
  const containerRect = containerNode.getBoundingClientRect();
  const x = event.clientX;
  const y = event.clientY;

  if (
    x < containerRect.left ||
    x > containerRect.right ||
    y < containerRect.top ||
    y > containerRect.bottom
  ) {
    outsideClickCallback();
  }
};
发布评论

评论列表(0)

  1. 暂无评论