Button with centered text and icon
P粉033429162
2023-08-15 11:01:09
<p>I am developing a button component with an icon and text. </p>
<p>The icon is placed on the left side of the button, and the text is aligned in the middle of the button. </p>
<p>If the text cannot fit into the button, it should not wrap but can be truncated. </p>
<p>The key condition for truncating text is that the text should be the same distance from the edge of the button as the icon is from the edge of the button. </p>
<p>So you cannot add equal inline padding in buttons. </p>
<p>If the text is not truncated, it will be aligned in the middle of the button. </p>
<p>However, once it starts overlapping the icon or failing to fit into the button, the text will be cut off. </p>
<p>In other words, in the normal state, the position of the icon remains unchanged, but when there is not enough space for the text, the icon will push the text to the truncated position. </p>
<p>Visual example of button</p>
<p>I tried using CSS to achieve this effect, but failed. </p>
<p>Finally, I added a ResizeObserver to each button that calculates whether there is enough space for the text. </p>
<p>If there is not enough space, a different style will be applied to the icon. </p>
<p>My ResizeObserver solution, try resizing the window</p>
<p>CodePen</p>
<pre class="brush:php;toolbar:false;">const CLASS_TEXT = `button__text`;
const CLASS_NO_FREE_SPACE = `button_no-free-space`;
const FREE_SPACE_SIZE = 70;
const buttons = document.querySelectorAll(`.${CLASS_ROOT}`);
const handleButtonResize = (entries) => {
entries.forEach((entry) => {
const { target } = entry;
const text = target.querySelector(`.${CLASS_TEXT}`);
const { width: widthButton } = entry.contentRect;
if (!(text instanceof HTMLElement)) {
return;
}
const widthText = text.offsetWidth;
const freeSpaceLeft = (widthButton - widthText) / 2;
const noFreeSpace = freeSpaceLeft <= FREE_SPACE_SIZE;
target.classList.toggle(CLASS_NO_FREE_SPACE, noFreeSpace);
});
};
const resizeObserver = new ResizeObserver(handleButtonResize);
[...buttons].forEach((button) => {
resizeObserver.observe(button);
});</pre>
<p><strong>My question is:</strong></p>
<p>Is it possible to achieve the same effect using pure CSS? </p>
Since the width of the icon is always the same (
2em
), we can use the::after
pseudo-element as a "buffer" for space balancing on the right.Set
.button__icon
to the flexible layout flow. This is crucial when "pushing" other elements. Give itmargin-right
to balance the left padding of the button.Create a
::after
pseudo-element withflex-basis: calc(2em 20px)
. Among them,2em
is the width of.button__icon
, and20px
is themargin-right
of.button__icon
. This balances the left and right spaces when.button__text
is short.Apply
justify-content: space-between
to the parent element to help balance.button__icon
,.button__text
and::after
When.button__text
is shorter.Add
flex-shrink: 999
, a huge shrink factor so that the layout engine will shrink::after
elements first when.button__text
is longer.