Here is a pure JS solution that rely on clip-path
and CSS variables, the idea is to duplicate the images to have one blurred and one not. Then we reveal the non-blurred one on the top:
var image =document.querySelector('.blur');
var p= image.getBoundingClientRect();
document.body.onmousemove = function(e) {
/*Adjust the clip-path*/
image.style.setProperty('--x',(e.clientX-p.top)+'px');
image.style.setProperty('--y',(e.clientY-p.left)+'px');
}
.blur {
display:inline-block;
width:400px;
height:200px;
position:relative;
}
.blur:before,
.blur:after{
content:"";
position:absolute;
top:0;
left:0;
right:0;
bottom:0;
background:var(--i);
}
.blur:before {
filter:blur(5px) grayscale(60%);
}
.blur:after {
clip-path: circle(60px at var(--x,-40px) var(--y,-40px));
}
<div class="blur" style="--i:url(https://picsum.photos/400/200?image=1069)">
</div>
With this solution you can easily do the oppsite if you want to blur a part of the image on hover:
var image =document.querySelector('.blur');
var p= image.getBoundingClientRect();
document.body.onmousemove = function(e) {
/*Adjust the clip-path*/
image.style.setProperty('--x',(e.clientX-p.top)+'px');
image.style.setProperty('--y',(e.clientY-p.left)+'px');
}
.blur {
display:inline-block;
margin:50px;
width:200px;
height:200px;
position:relative;
}
.blur:before,
.blur:after{
content:"";
position:absolute;
top:0;
left:0;
right:0;
bottom:0;
background:var(--i);
}
.blur:after {
filter:blur(5px);
}
.blur:after {
clip-path: circle(60px at var(--x,-40px) var(--y,-40px));
}
<div class="blur" style="--i:url(https://picsum.photos/200/200?image=1069)">
</div>