Home > Net >  Use a dynamically expanding div as clipping mask for background image in parent
Use a dynamically expanding div as clipping mask for background image in parent

Time:07-31

I'm looking for a CSS solution that adapts to div contents, with the functionality of clip-path but dynamic. This is my code:

.background {
  background: yellow;
  text-align: center;
}

.text {
  display: inline-block;
  margin: 20px;
  padding: 20px;
  background: teal;
}
<div >
<div >
My text is in here
</div>
</div>

Yellow and teal are just used for illustration. I want to replace the yellow background with an image, but only show it in the teal area. The div.background spans the width of the browser, but I cannot make assumptions about the width of div.text. Can this be done with only CSS or does it require JS and dynamically setting background-position?

CodePudding user response:

Use a pseudo element that you make relative to the background element

.background {
  background: yellow;
  text-align: center;
  position: relative;
  z-index: 0;
}

.text {
  display: inline-block;
  color: #fff;
  margin: 20px;
  padding: 20px;
  clip-path: inset(0); /* clip to only text element */
}

.text:before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: 0;
  background: url(https://picsum.photos/id/1056/800/600) center/cover;
}

/* to illustrate */
.text:hover {
  clip-path: none;
}
<div >
  <div >
    My text is in here
  </div>
</div>

CodePudding user response:

Here is one way of doing what you want through JS. The image is in the background element, and it is clipped according to the dimensions of the child element. There's a resize observer applied to the child element to trigger the calculation of the clipping mask whenever the dimensions of the child change.

I've added an animation to show how the clipping is calculated in real-time, but as you can see there is some slight stutter.

let text = document.querySelector('.text');
let bg = document.querySelector('.background');
let observer = new ResizeObserver(() => {
  calculateClipPath(bg, text);
})
observer.observe(text);

function calculateClipPath (parent, child) {
  parent.style.clipPath = `inset(
    ${child.offsetTop}px 
    ${parent.clientWidth - (child.offsetLeft   child.clientWidth)}px 
    ${parent.clientHeight - (child.offsetTop   child.clientHeight)}px 
    ${child.offsetLeft}px
  )`;
}
.background {
  background: url(https://c4.wallpaperflare.com/wallpaper/368/148/1024/flowers-roses-drawing-light-wallpaper-preview.jpg);
  text-align: center;
  position: relative;
}

.text {
  display: inline-block;
  margin: 20px;
  padding: 40px;
  width: 200px;
  animation: 3s infinite change;
}

@keyframes change {
  0% {
    width: 200px;
  }
  50% {
    width: 150px;
  }
  100% {
    width: 200px;
  } 
}
<div >
<div >
My text is in here
</div>
</div>

I'm still experimenting to see if there is a purely CSS version of the solution because that would always be smoother than the JS solution. If I can figure it out, I'll edit this answer and add it here

  • Related