Search code examples
htmlcsssassvuejs3vuetifyjs3

Fixing the vertical and horizontal position of an element over an image which is set to onbject-fit to contain


I have already several days looking for a solution CSS or component for making an image resizable (height and width) and over the image, elements keeping the position over the same X and Y coordinates of the image.

A simple code what I tried:

.img-container {
  position: relative;
  width: 100%;
  height: 100vh;
  background-color: antiquewhite;
}

.aspect-ratio-img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.overlay {
  position: absolute;
  top: 30%;
  left: 30%;
}
<div class="img-container">
  <img src="img.jpg" class="aspect-ratio-img" alt="Your Image">
  <div class="overlay">Element</div>
  </img>
</div>
Result: enter image description here

But this is wrong. In the example, the "element" text should have been kept over the same column and row of the grid. But as I show in the image it maintains the Top and Left relative to the browser edge and not based on the position of the image

What would like to have is something like next gif: enter image description here

  1. Background image maintains proportions and resize horizontally and vertically with the browser window
  2. Foreground elements keep the position in the same X,Y coordinates of the background image.And also resizes (like using transform)

If I find the solution, I will post it here.


Solution

  • I did a component with a slot which all you place there will be resized. I'm not using JQuery, but I use the library vue3-resize, you can also implement it without this library due to the VUE3 observer feature This solution was based in the post https://css-tricks.com/scaled-proportional-blocks-with-css-and-javascript/#article-header-id-1 Thanks a lot

        <script setup lang="ts">
        import { ref, onMounted } from 'vue'
        import 'vue3-resize/dist/vue3-resize.css'
        
        const bodyMapContainer = ref(null)
        const scalableContainer = ref(null)
        const scale = ref(0)
        
        onMounted(() => {
          doResize(bodyMapContainer.value.offsetWidth, bodyMapContainer.value.offsetHeight)
        })
        
        const handleResize = ({ width, height }) => {
          doResize(width, height)
        }
        
        const doResize = (width: number, height: number) => {
          const innerWidth = scalableContainer.value.offsetWidth
          const innerHeight = scalableContainer.value.offsetHeight
        
          scale.value = Math.min(width / innerWidth, height / innerHeight)
        }
        </script>
        
        <template>
          <div ref="bodyMapContainer" class="body-map-container">
            <resize-observer @notify="handleResize" />
            <div
              ref="scalableContainer"
              class="scalable-container"
              :style="{ transform: 'translate(-50%, -50%) ' + 'scale(' + scale + ')' }"
            >
              <slot></slot>
            </div>
          </div>
        </template>
        <style>
        .body-map-container {
          position: relative;
          width: 100%;
          box-sizing: border-box;
          margin: 0 auto;
          left: 0%;
        }
        .scalable-container {
          width: 1000px;
          height: 750px;
          padding: 5px;
          text-align: center;
          position: absolute;
          left: 50%;
          top: 50%;
          transform: translate(-50%, -50%);
          transform-origin: center center;
        }
        </style>
    

    Use:

            <ViewBoxPanel>
            ... place here everything you want
            <ViewBoxPanel>
    ``