'Creating an image map using Javascript, not an SVG (React)
I'm trying to create a map of button elements that overlay a full-screen image, positioned over all the 'buttons' depicted on the image. When the image resizes, the button elements should resize as well.
I initially used an SVG image map for this, but it had a positioning bug (the link below) that didn't allow for the video player inside the foreignObject to show.
SVG foreignObject and absolute positioning
I figured using absolute positioning, and JS to measure the width of the background image was the best way to go. However, I'm having trouble making it work.
Below is the project, with the white box needing to be positioned over the background image (the entire box, with chocolates, and the screen).
The image is within a div as a background, like so:
export const BackgroundLightsOn = styled.div`
position: absolute;
background: url(${BgLightsOn});
background-position: center center;
background-repeat: no-repeat;
background-size: cover;
height: 100%;
width: 100%;
border: 1px solid red;
`
Unfortunately, getting the dimensions of this DOM element gets the dimensions of the div, not the image (which is sometimes larger than the viewport). Below is the 'resizing' code, but this connects to backgroundRef (which is the div element the image is the background, not the image itself).
import React, { useEffect, useRef } from 'react';
import {
TruffleTinContainer,
TruffleTinOutside,
} from '../truffle-tin/styledTruffleTin';
export default function TruffleTin({ backgroundRef }) {
const truffleTinRef = useRef();
const initialPos = { x: 150, y: 150 };
const padding = 25;
let truffleBoxWidth = 0;
let truffleBoxHeight = 0;
useEffect(() => {
truffleBoxWidth = truffleTinRef.current.offsetWidth;
truffleBoxHeight = truffleTinRef.current.offsetHeight;
resize();
}, []);
function resize() {
let backgroundDivRect = backgroundRef.current.getBoundingClientRect();
truffleTinRef.current.style.left =
(initialPos.x / truffleBoxWidth) * backgroundDivRect.width -
padding +
'px';
truffleTinRef.current.style.top =
(initialPos.y / truffleBoxHeight) * backgroundDivRect.height -
padding +
'px';
}
useEffect(() => {
window.addEventListener('resize', resize);
return () => window.removeEventListener('resize', resize);
});
return (
<TruffleTinContainer ref={truffleTinRef}>
I'm the truffle tin
</TruffleTinContainer>
);
}
See the GIF below for the issue:
What's the best way to get this div to proportionally match the width of the background image?
(Edit: I changed the 'div with image background' to an img tag, but even then, the getBoundingClientRect shows not the width of the image, but the width of the viewport:
(in other words, even though the image is much wider than the viewport, I'm still getting the viewports dimensions, when what I want is the image's dimensions))
Solution 1:[1]
You don't need JS for that. This is achieved with plain CSS, all you need is the button center positions relative to left-top corner of the block. Sample HTML/CSS:
<div class='wrapper'> <!-- Will need this later -->
<div class='container'>
<a class='button' style='--left: 25%; --top: 20%'>1</a>
<a class='button' style='--left: 35%; --top: 80%'>2</a>
</div>
</div>
.container{
position: relative;
background: url('/path/to/image');
background-size: 100% auto;
}
.button{
position: absolute;
left: var(--left);
top: var(--top);
transform: translate(-50%, -50%); /* Move button a bit so its center matches the actual position */
/* These are just for visualization */
width: 50px;
height: 50px;
border-radius: 100%;
background: pink;
display: flex; align-items: center; justify-content: center;
}
The next step is to make .container have a constant width, or at least a constant width/height ratio if you care about vertical resizing. The constant width is achieved with a simple min-width. To avoid horizontal scroll we will add a .wrapper to hide the cut-off sides:
.wrapper{
display: flex;
align-items: center;
max-width: 100%;
overflow: hidden;
}
/* A-and a min-width for .container */
.container{
min-width: 1200px; /* Or whatever */
}
If you need a fixed ratio:
.container::before{
content: '';
display: block;
padding-top: 56.25%; /* 56.25% of the parent width this is, ratio 16x9 */
}
min-width can still be applied if needed. There's an aspect-ratio CSS property that can be used instead of that pseudo-element hack, but it is supported only in relatively fresh browser versions; still might be used if you don't plan supporting a little outdated browsers.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Jared |



