oldboy
oldboy

Reputation: 5954

SVG Gradient like Canvas Gradient?

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

Upvotes: 0

Views: 378

Answers (1)

Kaiido
Kaiido

Reputation: 137006

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.

Upvotes: 1

Related Questions