Problem: the middle bar is a pixel taller than the other bars. This is not because I got the css wrong, it's because browsers align the edges of rectangles to the pixel grid.
I've tried a few different ways of making a hamburger icon:
absolutely position a few divs with background-colors.
inline svg tag
svg as a background image
single div with top/bottom border and background-color for the bars, and padding and
background-clip: content-box;
for the spaces.
All of these methods have the same problem:
At some zoom-levels/sizes, the icon looks bad because the three rectangles aren't all the same size. They're different sizes sometimes because browsers like to align the edges of rectangles with the pixel grid.
I of course want the two spaces between the rectangles to be the same thickness too.
I get that this comes with the compromise that the icon must be a multiple of 5px tall. Or, if you can expand the spaces by one pixel at alternating breakpoints than when you expand the bars, then it can be 2X or 3X as many different sizes.
So what's the trick?
Can a webfont use hinting to make it so all three rectangles get the same number of pixels of thickness regardless of size/resolution/zoom?
Is there some sort of CSS math I can do?
I'm really hoping I don't have to have JavaScript that tries to detect changes in the zoom level.
EDIT: LIVE DEMO
var hamburgerIconEl = document.getElementById('hamburgerIcon');
var fontSizeEl = document.getElementById('fontSize');
fontSizeEl.addEventListener('input', function (e) {
hamburgerIconEl.style.fontSize = '' (8 parseFloat(fontSizeEl.value) / 100) 'px';
});
.body {
background: white;
}
#hamburgerIcon {
box-sizing: border-box;
display: inline-block;
height: 1.7ex;
width: 2.04ex;
border-width: .34ex 0;
border-style: solid;
border-color: black;
background-clip: content-box;
padding: .34ex 0;
background-color: black;
}
<p>Change the font size, watch the hamburger icon, see how there are a lot of sizes in which the bars don't all have the same thickness and/or spacing.</p>
<div>
<input type="range" id="fontSize" min="0" max="1000" value="0">
<label for="volume">Font Size</label>
</div>
<div>
<div id="hamburgerIcon" style="font-size: 8px"></div> Menu
</div>
CodePudding user response:
Now, I haven't seen your inline SVG, but could the issue be that you placed the lines too close to the edge of the SVG? If so, part of the top and bottom lines will seam thinner because they are cute off.
var hamburgerIconEl = document.getElementById('hamburgerIcon');
var fontSizeEl = document.getElementById('fontSize');
fontSizeEl.addEventListener('input', function (e) {
hamburgerIconEl.style.fontSize = '' (8 parseFloat(fontSizeEl.value) / 100) 'px';
});
svg {
height: 2em;
width: 2em;
font-size: 8px;
}
<div>
<input type="range" id="fontSize" min="0" max="1000" value="0">
<label for="volume">Font Size</label>
</div>
<div>
<svg viewBox="0 0 10 10" id="hamburgerIcon">
<path d="M 1 3 H 8 M 1 5 H 8 M 1 7 H 8" fill="none" stroke="black" stroke-width="1" />
</svg>
</div>
CodePudding user response:
Systems do struggle with what to do with part pixels - one CSS pixel can map to several screen pixels on modern devices.
When there is an attempt at showing a fraction of a pixel there has to be a decision on whether to leave a screen pixel 'behind' or not.
A slightly simpler way of drawing the hamburger, using background image linear gradient, removes the problem of the middle bar sometimes not appearing centered. There are however the odd occasions where the middle bar is not exactly the same height as the other two.
var hamburgerIconEl = document.getElementById('hamburgerIcon');
var fontSizeEl = document.getElementById('fontSize');
const label = document.querySelector('label');
fontSizeEl.addEventListener('input', function(e) {
hamburgerIconEl.style.fontSize = '' (8 parseFloat(fontSizeEl.value) / 100) 'px';
label.innerHTML = 'Font Size = ' hamburgerIconEl.style.fontSize;
});
#hamburgerIcon {
display: inline-block;
padding: 0;
margin: 0;
height: 1.7ex;
width: 2.05ex;
background-image: linear-gradient(black 0 20%, transparent 20% 40%, black 40% 60%, transparent 60% 80%, black 80% 100%);
}
<div>
<input type="range" id="fontSize" min="0" max="1000" value="0">
<label for="volume">Font Size</label>
</div>
<div id="hamburgerIcon"></div>