Part of SVG Mask Is Opaque and Color Is Inverted

2019-08-24 05:03发布

问题:

What I'm trying to do is add a textured frame or border to an SVG mask.

  1. Here is a working demo of what I'd like to achieve.

  2. Here is what I'm working with.

Notice that in the second demo the image used in the mask, #frame, does not seem to have any transparency whatsoever and the color is inverted (so that what is actually black displays as pure white), unlike the mask image, #rectangle > image, in the working demo.

However, the only difference that I can spot between both demos is that the first, working demo applies a feGaussianBlur to a g element. I've tried grouping #eye and #frame in the second demo, but this didn't seem to have any effect.

What am I missing?

回答1:

You need to see your <mask> as a standalone grayscale image, that will get applied to the target element.
There every black pixels will be removed from the target, while every white and transparent ones will stay untouched or in other words, the darker it is in the mask, the more transparent it will be on the target.

So here are both masks

.bg {
  width: 100%;
  height: 100%;
  fill: #666;
}
#background {
  fill: #999;
}
#eye {
  fill: #fff;
}
.fake-mask {
  filter: grayscale(100%);
}
svg{width: 40vw; display: inline-block}
<svg  viewBox='0 0 800 800'>
<defs>
    <filter id="blurMe">
      <feGaussianBlur in="SourceGraphic" stdDeviation="2" />
    </filter>
</defs>
<!--    <mask id="myMask"> -->
    <g class="fake-mask">
      <rect class='bg' width="800" height="800"/>
      <g id="rectangle" filter="url(#blurMe)">
        <rect  width="300" height="400" x="120" rx='10' ry='10' fill="white" />
        <image
        xlink:href='https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png'
        width="200" height="200"/>
      </g>
    </g>
<!--    </mask> -->
</svg><svg  viewBox='0 0 800 800'>
<!--  <mask id='mask'> -->
  <g class="fake-mask">
    <rect id='background' x='0' y='0' width='6144' height='4608' />
    <rect id='eye' x='0' y='0' width='500' height='500' />
    <image id='frame' xlink:href='https://newvitruvian.com/images/speckled-vector-distress.png' x='0' y='0' width='500' height='500' preserveAspectRatio='none' />
   </g>
<!--  </mask> -->
</svg>

As you can see, your image's border is darker than the background rectangle, this means that the target will be more transparent at this image's border than in the background.

To solve this issue, you would have to make the black pixels of your image become the same shade of gray as the background, so that the target can get an unified opacity.

And while it is possible to do it with filters, note that it may kill the performances.

const
  bdy = document.body,
  svg = document.getElementById('svg'),
  bkg = document.getElementById('background'),
  eye = document.getElementById('eye'),
  frm = document.getElementById('frame')
let
  eyeW = 0.35,
  eyeH = 0.75,
  mousednX = 0,
  mousednY = 0


// position maps on load
//
window.addEventListener('load', position)

function position(){
  const
    box = svg.getBoundingClientRect()
  svg.style.left = -(box.width - innerWidth) / 2 + 'px'
  svg.style.top = -(box.height - innerHeight) / 2 + 'px'
  
  const
    x = -(svg.getBoundingClientRect().left) + innerWidth * (1 - eyeW) / 2,
    y = -(svg.getBoundingClientRect().top) + innerHeight * (1 - eyeH) / 2
  eye.setAttribute('width', innerWidth * eyeW)
  eye.setAttribute('height', innerHeight * eyeH)
  eye.setAttribute('x', x)
  eye.setAttribute('y', y)
  frm.setAttribute('width', innerWidth * eyeW)
  frm.setAttribute('height', innerHeight * eyeH)
  frm.setAttribute('x', x)
  frm.setAttribute('y', y)
}

// drag functionality to explore map
//
bdy.addEventListener('mousedown', mousedown)
window.addEventListener('mouseup', mouseup)

function mousedown(e){
  e.preventDefault()
  mousednX = e.clientX
  mousednY = e.clientY
  bdy.addEventListener('mousemove', mousemove)
}

function mouseup(){
  bdy.removeEventListener('mousemove', mousemove)
}

function mousemove(e){
  adjustX = e.clientX - mousednX
  adjustY = e.clientY - mousednY
  if (svg.getBoundingClientRect().left + adjustX < 0 && svg.getBoundingClientRect().right + adjustX > innerWidth){
    svg.style.left = svg.getBoundingClientRect().left + adjustX + 'px'
  } else if (svg.getBoundingClientRect().left + adjustX >= 0){
    svg.style.left = 0 + 'px'
  } else {
    svg.style.left = -(svg.getBoundingClientRect().width - innerWidth)
  }
  if (svg.getBoundingClientRect().top + adjustY < 0 && svg.getBoundingClientRect().bottom + adjustY > innerHeight){
    svg.style.top = svg.getBoundingClientRect().top + adjustY + 'px'
  } else if (svg.getBoundingClientRect().top + adjustY >= 0){
    svg.style.top = 0 + 'px'
  } else {
    svg.style.top = -(svg.getBoundingClientRect().height - innerHeight)
  }
  mousednX = e.clientX
  mousednY = e.clientY
}

// center eye on cursor position
//
bdy.addEventListener('mousemove', moveEye)

function moveEye(e){
  const
    x = -(svg.getBoundingClientRect().left) + e.clientX - eyeW * innerWidth / 2,
    y = -(svg.getBoundingClientRect().top) + e.clientY - eyeH * innerHeight / 2
  eye.setAttribute('x', x)
  eye.setAttribute('y', y)
  frm.setAttribute('x', x)
  frm.setAttribute('y', y)
}
body {
  width: 100vw;
  height: 100vh;
  overflow: hidden;
  margin: 0;
}

#svg {
  width: 6144px;
  height: 4608px;
  position: absolute;
  left: -3072px;
  top: -2304px;
}



#eye {
  fill: #FFF;
}

#map {
  width: 6144px;
  height: 4608px;
  mask: url('#mask');
}
<svg id='svg' viewBox='0 0 6144 4608' version='1.1'>
  <filter id="contrast">
    <feComponentTransfer>
      <feFuncR type="linear" slope="0.4" intercept="0.2"/>
      <feFuncG type="linear" slope="0.4" intercept="0.2"/>
      <feFuncB type="linear" slope="0.4" intercept="0.2"/>
    </feComponentTransfer>
  </filter>
  <mask id='mask'>
    <g filter="url(#contrast)">
    <rect id='background' x='0' y='0' width='6144' height='4608' fill="#000"/>
    <rect id='eye' x='0' y='0' width='0' height='0' />
    <image id='frame' xlink:href='https://newvitruvian.com/images/speckled-vector-distress.png' x='0' y='0' width='0' height='0' preserveAspectRatio='none'/>
  </g>
  </mask>
  <image id='map' xlink:href='https://i.postimg.cc/hvH4yn2Q/map.jpg' x='0' y='0' width='6144' height='4608' mask="url(#myMask)"/>
</svg>



回答2:

Playing with your work in CodePen, I noticed that the white you get in the #frame image and the outer background comes from the default SVG document background color, which is not defined and appears to be white.

If you define the background-color style for the #svg element, say #f00 (red), you will see both the background and the #frame image as red.

Finally, I found that tweaking the opacity of the #frame image to 0.4 gives a better blend of the masked frame and background. You might re-paint the #frame image to better match the background at the borders of the image rectangle.

I just added the background-color (white) and opacity (0.4) in your CodePen as follows:

#svg {
  width: 6144px;
  height: 4608px;
  position: absolute;
  left: -3072px;
  top: -2304px;
  background-color: #fff;
}

#frame {
  opacity: .4;
}


标签: html css xml svg mask