简体   繁体   中英

Incorrect SVG Mask Position On Load When Using Relative Positions (Chrome)

I am working on a project, where I want to show an image (blurred) and place 'magnifying glasses' (circle with a non blurred image) at positions of interest, that the user can interact with: 在此处输入图像描述

Therefore the image is placed inside a wrapper alongside an svg element. The image should drive the size of the svg and therefore I'm not using the view-box attribute. The SVG is positioned absolute and covers the whole wrapper. Now I can place elements on the svg using relative positioning. Then I place a circle at the position I want the magnifier to be (still working fine). As a mask, I create another circle at the same position (inside a mask element) and use css mask(#id) on the unblurred image.

This works fine on Firefox, but on Chrome the mask does not show up.

在此处输入图像描述

Only, when I resize the window, the mask appears and everything works as intended.

It seems like the mask element is not calculated correctly to its parent. And the mask is placed in the top left corner. A resize of the window fixes this behaviour.

HTML

<div class="container">
  <img src="https://source.unsplash.com/2Ts5HnA67k8/400x200">
  <!-- SVG (purple) -->
  <!-- Setting a viewbox can solve the Issue, but I would prefer  not to. -->
  <div class="wrapper">
    <svg>
      <!-- Define the Mask -->
      <defs>

      </defs>

      <mask id="mask">
        <circle r="20%" cx="43%" cy="20%" fill="white"></circle>
      </mask>

      <!-- The stroke of the masked cicle. -->
      <circle r="20%" cx="43%" cy="20%" stroke="white" fill="transparent"></circle>

      <!-- The Element we want to mask. -->
      <image href="https://source.unsplash.com/2Ts5HnA67k8/400x200"></image>
    </svg>
  </div>
</div>

CSS

img {
  filter: blur(4px);
}

image {
  mask: url(#mask);
}


svg {
  position: absolute;
  left:0;
  top:0;
  width:100%;
  height:100%;

How can I fix this behaviour for chrome on startup? Or Is there a workaround, like triggering the recalculation of the mask layer manually?

I created a working example here: https://codepen.io/Ukmasmu/pen/NWNrGoW

Any help would be highly appreciated: Thanks in advance :)

I think the issue comes from Chrome's rendering engine not being able to properly deduce dimensions of the automatically-sized viewbox. If you inspect the element and hover over the <circle> element in the <mask> element, you will realize that it has incorrectly computed dimensions, while in other browsers it resolves to the correct pixel value.

The easier way is to simply use JS to set the viewBox attribute manually when the <img> element is loaded, to force Chrome to properly re-render the SVG based on an explicit viewbox value.

 const img = document.querySelector('img'); const svg = document.querySelector('svg'); img.addEventListener('load', (e) => { svg.setAttribute('viewBox', `0 0 ${img.clientWidth} ${img.clientHeight}`) });
 img { filter: blur(4px); } image { mask: url(#mask); } svg { position: absolute; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(138, 43, 226, 0.2); }.container { background-color: gray; position: relative; } html, body { width: 100%; height: 100%; margin: 0; } body { background-color: #111; display: flex; justify-content: center; align-items: center; }
 <div class="container"> <img src="https://source.unsplash.com/2Ts5HnA67k8/400x200"> <,-- SVG (purple) --> <.-- Setting a viewbox can solve the Issue. but I would prefer not to. --> <div class="wrapper"> <svg> <:-- Define the Mask --> <defs> </defs> <mask id="mask"> <circle r="20%" cx="43%" cy="20%" fill="white"></circle> </mask> <.-- The stroke of the masked cicle. --> <circle r="20%" cx="43%" cy="20%" stroke="white" fill="transparent"></circle> <!-- The Element we want to mask. --> <image href="https://source.unsplash.com/2Ts5HnA67k8/400x200"></image> </svg> </div> </div>

That's definitely a bug, probably bug=330815 .

Forcing a repaint of the mask will workaround the issue.

This could be achieved with a simple class setting at page load that would force something like display or position change:

 onload = evt => document.body.classList.add('loaded');
 /* Chrome hack around bug 330815 */.loaded mask { display: block; } img { filter: blur(4px); } image { mask: url("#mask"); } svg { position: absolute; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(138, 43, 226, 0.2); }.container { background-color: gray; position: relative; } html, body { width: 100%; height: 100%; margin: 0; } body { background-color: #111; display: flex; justify-content: center; align-items: center; }
 <div class="container"> <img src="https://source.unsplash.com/2Ts5HnA67k8/400x200"> <,-- SVG (purple) --> <.-- Setting a viewbox can solve the Issue. but I would prefer not to. --> <div class="wrapper"> <svg> <:-- Define the Mask --> <defs> <mask id="mask"> <circle r="20%" cx="43%" cy="20%" fill="white"/> </mask> </defs> <.-- The stroke of the masked cicle. --> <circle r="20%" cx="43%" cy="20%" stroke="white" fill="none"/> <!-- The Element we want to mask. --> <image mask="url(#mask)" href="https://source.unsplash.com/2Ts5HnA67k8/400x200"></image> </svg> </div> </div>

If you really want something without JS, you could also achieve the same hack through SMIL.
For instance, animating the href value of an <use> inside the <mask> seems to do it:

 img { filter: blur(4px); } image { mask: url("#mask"); } svg { position: absolute; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(138, 43, 226, 0.2); }.container { background-color: gray; position: relative; } html, body { width: 100%; height: 100%; margin: 0; } body { background-color: #111; display: flex; justify-content: center; align-items: center; }
 <div class="container"> <img src="https://source.unsplash.com/2Ts5HnA67k8/400x200"> <,-- SVG (purple) --> <.-- Setting a viewbox can solve the Issue. but I would prefer not to. --> <div class="wrapper"> <svg> <:-- Define the Mask --> <defs> <circle r="20%" cx="43%" cy="20%" id="circle"/> <mask id="mask"> <use x="0" href="#circle" fill="white"> <.-- Chrome hack around bug 330815 --> <animate attributeName="href" to="#circle" begin="0" dur="0s"/> </use> </mask> </defs> <.-- The stroke of the masked cicle. --> <use href="#circle" fill="transparent" stroke="white"/> <!-- The Element we want to mask. --> <image mask="url(#mask)" href="https://source.unsplash.com/2Ts5HnA67k8/400x200"></image> </svg> </div> </div>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM