Adjust size of element in relation to Background Cover using jQuery

Can you help me solve a challenge I'm facing? I have a website with a fullsize background image, and I need to attach a div to a specific position on the image while ensuring that it scales in the same way as the background image with the "background-size: cover" property.

I've been able to position the div correctly, but resizing it has proven difficult. Here's what I've tried so far:

http://codepen.io/EmmieBln/pen/YqWaYZ

var imageWidth = 1920,
    imageHeight = 1368,
    imageAspectRatio = imageWidth / imageHeight,
    $window = $(window);

var hotSpots = [{
    'x': -160,
    'y': -20,
    'height': 400,
    'width': 300
}];

function appendHotSpots() {
    for (var i = 0; i < hotSpots.length; i++) {
        var $hotSpot = $('<div>').addClass('hot-spot');
        $('.container').append($hotSpot);
    }
    positionHotSpots();
}

function positionHotSpots() {
    var windowWidth = $window.width(),
        windowHeight = $window.height(),
        windowAspectRatio = windowWidth / windowHeight,
        $hotSpot = $('.hot-spot');

    $hotSpot.each(function(index) {
        var xPos = hotSpots[index]['x'],
            yPos = hotSpots[index]['y'],
            xSize = hotSpots[index]['width'],
            ySize = hotSpots[index]['height'],
            desiredLeft = 0,
            desiredTop = 0;

        if (windowAspectRatio > imageAspectRatio) {
            yPos = (yPos / imageHeight) * 100;
            xPos = (xPos / imageWidth) * 100;
            xSize = (xSize / imageWidth) * 1000;
            ySize = (ySize / imageHeight) * 1000;
        } else {
            yPos = ((yPos / (windowAspectRatio / imageAspectRatio)) / imageHeight) * 100;
            xPos = ((xPos / (windowAspectRatio / imageAspectRatio)) / imageWidth) * 100;
        }

        $(this).css({
            'margin-top': yPos + '%',
            'margin-left': xPos + '%',
            'width': xSize + 'px',
            'height': ySize + 'px'
        });

    });
}

appendHotSpots();
$(window).resize(positionHotSpots);

I thought about calculating a scale value based on the ratios of imageWidth/windowWidth and imageHeight/windowHeight and then using this value for scaling, but I couldn't get it to work...

I would appreciate any help or guidance on this matter.

Answer №1

Tackling background-size:cover Issue

In my attempt to address your concern, I have come up with a solution (or you can consider it as an idea). Feel free to explore the working demo here. Make sure to resize the window to observe the outcome.

Initially, I was puzzled by the use of `transform`, `top:50%`, and `left:50%` for hotspot. Hence, I simplified the approach by adjusting the markup and CSS for better understanding.

In this scenario, `rImage` represents the aspect ratio of the original image.

 var imageWidth = 1920;
 var imageHeight = 1368;
 var h = {
   x: imageWidth / 2,
   y: imageHeight / 2,
   height: 100,
   width: 50
 };
 var rImage= imageWidth / imageHeight;

Within the window resize handler, the aspect ratio of the viewport (`r`) is calculated. Subsequently, we need to determine the image dimensions when resizing the window while keeping the aspect ratio in check. For such calculations using `background-size:cover`, the following formulas are used:

if(actual_image_aspectratio <= viewport_aspectratio)
    image_width = width_of_viewport
    image_height = width_ofviewport / actual_image_aspectratio 

Also,

if(actual_image_aspectratio > viewport_aspectratio)
    image_width = height_of_viewport * actual_image_aspectratio 
    image_height = height_of_viewport

For more insights on image dimension calculations concerning `background-size:cover`, refer to this URL.

Upon obtaining the image dimensions, we proceed to map the hot-spot coordinates from the old image to the new image dimensions.

When fitting the image into the viewport, it may be clipped either vertically or horizontally. Therefore, these clipped areas should be considered as offsets when plotting hotspots.

offset_top=(image_height-viewport_height)/2
offset_left=(image_width-viewport_width)/2

Add these offset values to each hotspot's `x,y` coordinates.

-- Insert remaining content here --

Answer №2

Many individuals may not be aware of the CSS units vh and vw, which stand for ViewportHeight and ViewportWidth respectively. I've devised a script that executes once upon pageload (as opposed to other solutions that run with every resize).

This script computes the background-image ratio, applies two CSS rules to overlayContainer, and finishes its task.

Additionally, there is a div labeled #square, included to create a canvas container with a 1:1 aspect ratio. This ensures consistency in vertical and horizontal percentage distances when overlaying elements.

If you are using background-size: cover, refer to this Fiddle.

For background-size: contain, see this Fiddle.

The HTML structure:

<div id="overlayContainer">
  <div id="square">
    <!-- Add overlaying elements here -->
  </div>
</div>

The required CSS styling:

#overlayContainer {
  position: absolute; /* Use fixed if background-image is also fixed */
  min-width:  100vw; /* Applicable for cover */
  min-height: 100vh; /* Applicable for cover */
  max-width:  100vw; /* Applicable for contain */
  max-height: 100vh; /* Applicable for contain */
  top:  50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

#square {
  position: relative;
  padding-bottom: 100%;
}

/* When positioning overlayed elements, ensure they are all absolutely positioned using percentages only */
/* Refer to provided fiddles for detailed examples */

The JavaScript implementation (using jQuery):

var image = new Image()
image.src = $('body').css('background-image').replace(/url\((['"])?(.*?)\1\)/gi,'$2').split(',')[0]

/* For cover usage: */
$('#overlayContainer').css({'height': 100/(image.width/image.height)+'vw', 'width': 100/(image.height/image.width)+'vh'})

/* For contain usage: */
$('#overlayContainer').css({'height': 100*(image.height/image.width)+'vw', 'width': 100*(image.width/image.height)+'vh'})

I trust this information proves beneficial.


Update by @LGSon

It came as a surprise to discover a CSS-centric solution lurking within this answer itself, prompting me to include it herein.

Incorporating these 2 lines into the #overlayContainer rule (suitable for both cover and

contain</code) eliminates the need for the script.</p>

<pre><code>width:  calc(100vh * (1920 / 1368));
height: calc(100vw * (1368 / 1920));

Although the script version automatically fetches values, since hotspots typically have a specific location within the background, the image dimensions are usually known.

Example involving background-size: cover:

html, body {
  height: 100%;
  overflow: hidden;
}

body {
  margin: 0;
  background-image: url('https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/Alexanderplatz_Stadtmodell_1.jpg/1920px-Alexanderplatz_Stadtmodell_1.jpg');
  background-size: cover;
  background-repeat: no-repeat;
  background-position: center;
}

#overlayContainer {
  position: absolute;
  width:  calc(100vh * (1920 / 1368));
  height: calc(100vw * (1368 / 1920));
  min-width:  100vw;     /*  applicable for cover  */
  min-height: 100vh;     /*  applicable for cover  */
  /* max-width:  100vw;      applicable for contain  */
  /* max-height: 100vh;      applicable for contain  */
  top:  50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

#square {
  position: relative;
  padding-bottom: 100%;
}

#square div {
  position: absolute;
  top: 19.75%;
  left: 49.75%;
  width: 4.75%;
  height: 4.75%;
  background-color: rgba(255,0,0,.7);
  border-radius: 50%;
}
<div id="overlayContainer">
  <div id="square">
    <div></div>
  </div>
</div>

Answer №3

Hey there! I decided to take your original concept and make a few tweaks to enhance it.

Instead of using percentages, I opted for pixel values as they seemed more straightforward. Here's the updated code snippet:

$(this).css({
  'margin-top': yPos + 'px',
  'margin-left': xPos + 'px',
  'width': xSize + 'px',
  'height': ySize + 'px'
});

Now, all we need to do is analyze the viewport proportions to adjust the properties of the div:

if (windowAspectRatio > imageAspectRatio) {
  var ratio = windowWidth / imageWidth;
} else {
  var ratio = windowHeight / imageHeight;
}

xPos = xPos * ratio;
yPos = yPos * ratio;
xSize = xSize * ratio;
ySize = ySize * ratio;

Check out this live demo for a practical example: http://codepen.io/jaimerodas/pen/RaGQVm

Here's a Stack snippet that illustrates the implementation further:

var imageWidth = 1920,
    imageHeight = 1368,
    imageAspectRatio = imageWidth / imageHeight,
    $window = $(window);

var hotSpots = [{
  x: -210,
  y: -150,
  height: 250,
  width: 120
}, {
  x: 240,
  y: 75,
  height: 85,
  width: 175
}];

function appendHotSpots() {
  for (var i = 0; i < hotSpots.length; i++) {
    var $hotSpot = $('<div>').addClass('hot-spot');
    $('.container').append($hotSpot);
  }
  positionHotSpots();
}

// More functions and logic go here

appendHotSpots();
$(window).resize(positionHotSpots);
html, body {
  margin: 0;
  width: 100%;
  height: 100%;
}

.container {
  width: 100%;
  height: 100%;
  position: relative;
  background-image: url(https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/Alexanderplatz_Stadtmodell_1.jpg/1920px-Alexanderplatz_Stadtmodell_1.jpg);
  background-size: cover;
  background-repeat: no-repeat;
  background-position: center;
}

.hot-spot {
  background-color: red;
  border-radius: 0;
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 1;
  opacity: 0.8;
  content: "";
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container"></div>

Answer №4

Utilizing css transforms on a single element can significantly enhance performance, regardless of the number of hotspots present. This approach minimizes DOM manipulations and reduces reflows. Additionally, leveraging hardware acceleration is an added bonus.

Here is a high-level overview of the process:

  1. Set up a .hot-spot--container within your image .container

  2. Create .hot-spot elements and position/size them inside the .hot-spot--container

  3. Apply transforms to the .hot-spot--container to mimic the behavior of background-size: cover

  4. Periodically readjust based on window resizing

Determine the ratio of your background image:

var bgHeight = 1368;
var bgWidth = 1920;
var bgRatio = bgHeight / bgWidth;

Calculate container ratio during window resize:

var containerHeight = $container.height();
var containerWidth = $container.width();
var containerRatio = containerHeight / containerWidth;

Compute scaling factors to replicate background-size: cover effect...

if (containerRatio > bgRatio) {
    xScale = (containerHeight / bgRatio) / containerWidth
} else {
    yScale = (containerWidth * bgRatio) / containerHeight
}

...and apply the transform to the hot spot container element to adjust its size and position accordingly:

var transform = 'scale(' + xScale + ', ' + yScale + ')';

$hotSpotContainer.css({
    'transform': transform
});

For hands-on experimentation, refer to this fiddle: https://jsfiddle.net/ovfiddle/a3pdLodm/. Feel free to modify the code to accommodate pixel-based dimensions and positioning for hot spots by accounting for container and image sizes in scale calculations.

Update: To achieve a background-size: contain effect, follow similar calculations with adjustments when the containerRatio is smaller than the bgRatio. See this updated fiddle for details: Updating the background css and reversing the sign will suffice.

Answer №5

Here is a simple jQuery solution using the bgCoverTool plugin to reposition an element based on the scale of its parent's background image.

//bgCoverTool Properties
$('.hot-spot').bgCoverTool({
  parent: $('#container'),
  top: '100px',
  left: '100px',
  height: '100px',
  width: '100px'})

You can see a demo below:

$(function() {
  $('.hot-spot').bgCoverTool();
});
html,
body {
  height: 100%;
  padding: 0;
  margin: 0;
}
#container {
  height: 100%;
  width: 100%;
  background: url('https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/Alexanderplatz_Stadtmodell_1.jpg/1920px-Alexanderplatz_Stadtmodell_1.jpg');
  background-size: cover;
  background-repeat: no-repeat;
  position: relative;
}
.hot-spot {
  position: absolute;
  z-index: 1;
  background: red;
  left: 980px;
  top: 400px;
  height: 40px;
  width: 40px;
  opacity: 0.7;
}
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>BG Cover Tool</title>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
  <script type="text/javascript" charset="utf-8">
    //bgCoverTool jQuery plugin
    (function($) {
      $.bgCoverTool = function(element, options) {
        var $element = $(element),
          imgsize = {};
        var defaults = {
          parent: $element.parent(),
          top: $element.css('top'),
          left: $element.css('left'),
          height: $element.css('height'),
          width: $element.css('width')
        };
        var plugin = this;
        plugin.settings = {};
        plugin.init = function() {
          plugin.settings = $.extend({}, defaults, options);
          var tempurl = plugin.settings.parent.css('background-image').slice(4, -1)
          .replace('"', '').replace('"', '');
          var tempimg = new Image();
          var console = console || {
            error: function() {}
          };
          if (plugin.settings.parent.css('background-size') != "cover") {
            return false;
          }
          if (typeof tempurl !== "string") {
            return false;
          }
          if (plugin.settings.top == "auto" || plugin.settings.left == "auto") {
            console.error("#" + $element.attr('id') + " needs CSS values for 'top' and 'left'");
            return false;
          }
          $(tempimg).on('load', function() {
            imgsize.width = this.width;
            imgsize.height = this.height;
            imageSizeDetected(imgsize.width, imgsize.height);
          });
          $(window).on('resize', function() {
            if ('width' in imgsize && imgsize.width != 0) {
              imageSizeDetected(imgsize.width, imgsize.height);
            }
          });
          tempimg.src = tempurl;
        };
        var imageSizeDetected = function(w, h) {
          var scale_h = plugin.settings.parent.width() / w,
            scale_v = plugin.settings.parent.height() / h,
            scale = scale_h > scale_v ? scale_h : scale_v;
          $element.css({
            top: parseInt(plugin.settings.top, 10) * scale,
            left: parseInt(plugin.settings.left, 10) * scale,
            height: parseInt(plugin.settings.height, 10) * scale,
            width: parseInt(plugin.settings.width, 10) * scale
          });

        };
        plugin.init();
      };
      /**
       * @param {options} object Three optional properties are parent, top and left.
       */
      $.fn.bgCoverTool = function(options) {
        return this.each(function() {
          if (undefined == $(this).data('bgCoverTool')) {
            var plugin = new $.bgCoverTool(this, options);
            $(this).data('bgCoverTool', plugin);
          }
        });
      }
    })(jQuery);
  </script>
</head>

<body>
  <div id="container">
    <div class="hot-spot"></div>
  </div>
</body>

</html>

Answer №6

An easier and more effective solution for your issue is to incorporate an SVG element, which aligns better with your needs. The great thing about SVG is that everything scales proportionally by default because it is a vector object rather than a document flow object.

Below is a demonstration of this technique

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>SVG Element</title>
        <style type="text/css" media="screen">
            body {
                background: #eee;
                margin: 0;
            }
            svg {
                display: block;
                border: 1px solid #ccc;
                position: absolute;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                background: #fff;
            }
            .face {
                stroke: #000;
                stroke-width: 20px;
                stroke-linecap: round
            }
        </style>
    </head>
    <body>
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="-350 -250 700 500">
            <circle r="200" class="face" fill="red"/>
            <path fill="none" class="face" transform="translate(-396,-230)" d="M487.41,282.411c-15.07,36.137-50.735,61.537-92.333,61.537 c-41.421,0-76.961-25.185-92.142-61.076"/>
            <circle id="leftEye" cx="-60" cy="-50" r="20" fill="#00F"/>
            <circle id="rightEye" cx="60" cy="-50" r="20" fill="#00F"/>
        </svg>
        <script type="text/javascript">
            document.getElementById('leftEye').addEventListener('mouseover', function (e) {
                alert('Left Eye');
            });
            document.getElementById('rightEye').addEventListener('mouseover', function (e) {
                alert('Right Eye');
            });
        </script>
    </body>
</html>

You can embed images within the SVG to accomplish your requirements.

https://jsfiddle.net/tnt1/3f23amue/

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the solution to removing the bottom margin from the jumbotron in the bootstrap framework?

I am building a website for my high school robotics team and I am new to HTML and CSS (bootstrap). I want to get rid of the gap between my banner and navbar on my site. Is there a way to remove that space? Check out our website: www.robotichive3774.com & ...

Enhancing the functionality of localStorage

I am attempting to append a value to a key within the HTML5 localStorage. Take a look at my code below: var splice_string = []; splice_string += "Test value"; var original = JSON.parse(localStorage.getItem('product_1')); origina ...

What is the best way to position my header at the top of my navigation bar?

I am new to the world of HTML and CSS! My goal is as follows: https://i.stack.imgur.com/hmLNS.png This is my progress so far: https://i.stack.imgur.com/rav8P.png I am also looking to have the header fill the entire browser window and remain fixed, wit ...

jQuery experiences compatibility issues with IE7, IE8, and IE9 while functioning smoothly on other web browsers

An issue has been encountered with this query in IE7, prompting an error message stating "Object Expected". While in IE8/9 there is no error but the query does not function properly. However, it works fine in all other modern browsers. If (jQuery("#tabs ...

Troubleshooting issues with browser scrollbars in a Silverlight application (HTML)

I am currently working on a simple silverlight application and I need to incorporate web browser scroll bars into it (scroll bars should not be inside my silverlight app). This is the HTML code I have: <style type="text/css"> html, body { heigh ...

Is there an easy method to verify if the local storage is devoid of any data?

Query is named aptly; seeking to verify in a conditional statement. ...

Issue with Webpack: error message "Cannot read property 'readFile' of undefined" is causing no output files to be generated

When utilizing version webpack > 5, the configuration for my appDevMiddleware.js is as follows: const path = require('path'); const webpack = require('webpack'); const webpackDevMiddleware = require('webpack-dev-middleware' ...

Saving information in binary format to a file

I have a script for setting up an installation. This specific script is designed to access a website where users can input values and upload a certificate for HTTPS. However, the outcome seems to be different from the expected input file. Below is the cod ...

When attempting to input data into the database, an error is displayed stating that /test.php cannot be POSTed

Every time I try to input data using PHP, it throws an error Cannot POST /test.php. I've been attempting to fix it with no success. Can anyone please help me solve this issue? It's crucial for my project work. Here is my HTML code: <html> ...

Having trouble with AngularJS? Ng-switch not updating after ng-click?

Initially in my code, I check if a user has the ability to flag a discussion. If they do, I use ng-switch where upon flagging, a success message is displayed: <div ng-if="canFlag(discussion)"> <div ng-switch="isFlagging" ng-click="fla ...

Retrieve PHP variable from a PHP CSS file

I'm facing an issue where I am unable to retrieve a PHP variable from a CSS file that is loaded in this manner from my index.php: <link href="css/style.php" rel="stylesheet"> Within the style.php file, the code looks like this: <?php heade ...

Unable to shift the header menus upwards for proper alignment with the logo

Header with logo I'm trying to align the menu items like Home, etc with the logo, but it seems like the logo is taking up too much space above the menu headers and I'm not sure how to reduce it. I've already attempted using margin-left or r ...

How to Redirect Multiple URLs Simultaneously using Laravel

Upon clicking a button, the user will be redirected to generate a PDF in a new tab and simultaneously redirected back to the dashboard as well. ...

Enhance the appearance of your custom Component in React Native by applying styles to Styled Components

I have a JavaScript file named button.js: import React from "react"; import styled from "styled-components"; const StyledButton = styled.TouchableOpacity` border: 1px solid #fff; border-radius: 10px; padding-horizontal: 10px; padding-vertical: 5p ...

How can we enhance Backbone.sync() at the Model level to include additional parameters?

Currently, I am facing a challenge with overriding Backbone's sync() method for a Model. I have the function signature set up and it is triggered correctly, but I am unsure about what needs to be included in the function body in order for it to automa ...

The Great Gatsby - Unable to access property 'component---src-pages-index-jsx' due to its undefined nature

Attempting to transition my current ReactJS application with a WordPress backend to GatsbyJS has presented some challenges. As a newcomer to GatsbyJS, I diligently followed the setup instructions provided on their website for Windows 10. Despite a successf ...

Iterate through an HTML table and transfer matching items along with their corresponding calculated amounts to a separate table

<html> <body> <div> <table border="1" id="topTable"> <thead> <th>Item</th> <th>Sold</th> </thead> <tbody id="topTableBody"> <tr> ...

Next.js server component allows for the modification of search parameters without causing a re-fetch of the data

I have a situation where I need to store form values in the URL to prevent data loss when the page is accidentally refreshed. Here's how I am currently handling it: // Form.tsx "use client" export default function Form(){ const pathname ...

Make sure to save the HTML content after we make an AJAX call to retrieve it

Is there a way to call an HTML file using Ajax and then save the response as an HTML file in a specific location? I have been trying to make an Ajax call using jQuery, like shown below: $.ajax({ type: "POST", url: "../../../project/html/T ...

Fill out a dropdown menu by selecting options from another dropdown menu

I'm currently working on populating a dropdown list based on the selection of another dropdown list. Here's what I have so far: Below is the function that takes the selected value from the first dropdown list as a parameter: [AcceptVerbs(HttpVe ...