You can use border
to produce the 'overlay' portion - that way you have a true 'transparent' block that darkens everything outside it. The trick is to have the borders extend to the edges of the element (or the screen) - you can do that by specifying the border-width in viewport units, like this:
.container {
position: relative;
width: 100vw;
height: 100vh;
overflow: hidden;
}
.container img{
margin: auto;
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
.loupe {
width: 24rem;
height: 18rem;
border: 0 solid rgba(0,0,0,0.5);
border-width: 50vh 50vw;
position: absolute;
top: -9rem; /* Half of height */
bottom: 0;
right: 0;
left: -12rem; /* Half of width */
margin: auto;
}
/* Optional - apply a shadow effect under the loupe */
.loupe::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
box-shadow: 0 0.1rem 1.5rem rgba(0,0,0,0.25);
}
<div class="container">
<img src="https://images.unsplash.com/photo-1621135177072-57c9b6242e7a?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1534&q=80"/>
<div class="loupe">
</div>
</div>
If you need to fine-tune the position of the loupe, you can use transform: translate();
to adjust the position.