0

Here is what I've built. You can drag the image around to explore the whole image.

<?xml version='1.0' standalone='no'?>
<svg version='1.1'>
  <image xlink:href='https://i.postimg.cc/hvH4yn2Q/map.jpg'
    id='background-image' />
  <clipPath>
    <rect />
  </clipPath>
  <image xlink:href='https://i.postimg.cc/hvH4yn2Q/map.jpg'
    id='main-image'/>
</svg>

Instead of the clipped rectangle having solid edges, what I would like to do is something like this, except for SVG. The caveat is that it must be responsive since the clipped rectangle is responsive.

Is it possible to do something similar in SVG?

One idea that comes to mind is something similar to either of the following images, whereby multiple gradients would be used, but it seems like a whole lot of work for something that can be done so easily in canvas.

enter image description here

enter image description here

oldboy
  • 5,729
  • 6
  • 38
  • 86
  • 1
    Your codepen does not work in FireFox. – Kosh Jan 19 '19 at 00:00
  • @KoshVery it's the image. the image doesn't seem to be loading for some reason :/ why would that be? – oldboy Jan 19 '19 at 00:17
  • @KoshVery i posted [another Q here](https://stackoverflow.com/questions/54263136/svg-image-not-loading-in-firefox) regarding that if youre interested – oldboy Jan 19 '19 at 02:01
  • 1
    You must set width and height attribute in svg1. FF still doesn't support this part of svg2 – Kaiido Jan 19 '19 at 02:26

1 Answers1

1

What you want is a <mask>.

In this mask, you will append your small rounded <rect> filled in black, with an feGaussianBlur applied on it.

const
  bdy = document.body,
  svg = document.getElementById('svg'),
  crc = document.getElementById('circle'),
  rec = document.getElementById('rectangle')
let
  mousednX = 0,
  mousednY = 0

window.addEventListener('load', position)
bdy.addEventListener('mousedown', mousedown)
window.addEventListener('mouseup', mouseup)
bdy.addEventListener('mousemove', moveEye)

function position(){
  const
    box = svg.getBoundingClientRect()
  svg.style.left = -(box.width - innerWidth) / 2 + 'px'
  svg.style.top = -(box.height - innerHeight) / 2 + 'px'
}

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
}

function moveEye(e){
  rec.setAttribute('x', -(svg.getBoundingClientRect().left) + e.clientX - rec.getBoundingClientRect().width / 2)
  rec.setAttribute('y', -(svg.getBoundingClientRect().top) + e.clientY - rec.getBoundingClientRect().height / 2)
}
body {
  width: 100vw;
  height: 100vh;
  overflow: hidden;
  margin: 0;
}

#svg {
  width: 6144px;
  height: 4608px;
  position: absolute;
  left: -3072px; /* set with JS */
  top: -2304px; /* set with JS */
}

#background-image {
  width: 6144px;
  height: 4608px;
  opacity: 0.25;
}

#rectangle {
  width: 35vw;
  height: 75vh;
}

#main-image {
  width: 6144px;
  height: 4608px;
  mask: url(#myMask);
}
#myMask .bg {
  width: 100%;
  height: 100%;
}
<svg id='svg' viewBox='0 0 6144 4608' version='1.1'>
  <defs>
    <filter id="blurMe">
      <feGaussianBlur in="SourceGraphic" stdDeviation="5" />
    </filter>
    <mask id="myMask">
      <rect class='bg'/>
      <rect id='rectangle' x='3172' y='2404' rx='10' ry='10' fill="white" filter="url(#blurMe)"/>
    </mask>
  </defs>
  <image x='0' y='0' preserveAspectRatio='none'
    xlink:href='https://i.postimg.cc/hvH4yn2Q/map.jpg'
    id='background-image' />
  <image x='0' y='0' preserveAspectRatio='none'
    xlink:href='https://i.postimg.cc/hvH4yn2Q/map.jpg'
    id='main-image'/>
</svg>

But note that setting the dimensions of your svg elements through CSS is a new feature of SVG2 and that all browsers still didn't implemented it (e.g Firefox). So here is an SVG1 compliant version, but there the vw/vh units won't work.

<svg width="500" height="500" viewBox="0 0 500 500">
  <defs>
    <filter id="blurMe">
      <feGaussianBlur in="SourceGraphic" stdDeviation="5" />
    </filter>
    <mask id="myMask">
      <rect width="500" height="500" fill="black"/>
      <rect y="100" fill="white" width="50" height="50" x="35" y="35" rx="5" ry="5" filter="url(#blurMe)"/>
    </mask>
  </defs>  
  <image xlink:href='https://i.postimg.cc/hvH4yn2Q/map.jpg'
    id='background-image' width="500" height="500" style="opacity:0.3"/>
  <image xlink:href='https://i.postimg.cc/hvH4yn2Q/map.jpg'
    id='main-image' width="500" height="500" mask="url(#myMask)"/>
</svg>

And you could even make this all with a single image, by setting the background's fill color to some shade of gray:

<svg width="500" height="500" viewBox="0 0 500 500">
  <defs>
    <filter id="blurMe">
      <feGaussianBlur in="SourceGraphic" stdDeviation="5" />
    </filter>
    <mask id="myMask">
      <rect width="500" height="500" fill="#333"/>
      <rect y="100" fill="white" width="50" height="50" x="35" y="35" rx="5" ry="5" filter="url(#blurMe)"/>
    </mask>
  </defs>  
  <image xlink:href='https://i.postimg.cc/hvH4yn2Q/map.jpg'
    id='main-image' width="500" height="500" mask="url(#myMask)"/>
</svg>

Here is the interactive version with a single image.

Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • in ur first demo, the part of the map that is covered by the white is one `image` element, whereas the part that is not white is the other `image` correct? another caveat is that im going to be animating other elements around the edge of the "eye"/rectangle – oldboy Jan 19 '19 at 03:39
  • What is white inside the `` is what will be kept as opaque in the masked element. What is black becomes transparent. So I first drew a full rect in black (its sizes are set in CSS), and your #rectangle in white. Then we apply this mask on the foreground image. But actually, using a gray rectangle as background, we could even do it all with a single ``: https://jsfiddle.net/1gfx7jrL/ And for your animation thing, I'm not sure to get it, but you can add whatever you wish inside the mask: http://jsfiddle.net/1gfx7jrL/1/ – Kaiido Jan 19 '19 at 04:05
  • i have yet to read over your comment in depth or word for word, but the reason i'm staying away from a mask and also using 2 images is because there will be icons displayed on one image (the image that is clipped) that i don't want to be visible on the white part of the image or outside of the "eye" window – oldboy Jan 20 '19 at 23:15
  • well, it turns out my method wasn't even going to allow me to do what i wanted to do, therefore using a mask instead is most practical. however, i can't seem to get the additional image (you used an image of dice in your example) to also be the same level of transparency??? [here is what i'm working with right now](https://codepen.io/tOkyO1/full/MLWwgx) any idea what's going on here??? – oldboy Jan 21 '19 at 23:35
  • You need to see your mask as a standalone image, that will get applied to the target. There every black pixels will be removed on the target, while every white and transparent ones will stay untouched. So if you need an unified mask, you need to make [this image](https://codepen.io/_-0-_/pen/Vgwvdg) unified. This should be done by changing your image so that what is currently black in there actually be the same gray as #background (`#999`), even if [it is technically possible](https://codepen.io/_-0-_/pen/OdJyeM) to apply a filter to do it, its a perf killer (on my FF, Chrome is good here). – Kaiido Jan 22 '19 at 01:34
  • i posted [the question here](https://stackoverflow.com/questions/54299907/part-of-svg-mask-is-opaque-and-color-is-inverted) if youre interested in getting the points for it – oldboy Jan 22 '19 at 01:50
  • what exactly do u mean by "unified"? ive tried using `fill: [not white]` but this didnt do anything for me :/ – oldboy Jan 22 '19 at 01:52
  • omg i believe i was thinking about this in all the wrong ways. im pretty sure the mask is actually working, but instead of the masked part being transparent like the background part (the other part of the mask around the eye viewport/frame) it just turning white cuz thats the bg color? – oldboy Jan 22 '19 at 01:53