I have a grid that draws squares in cells. It has the number of rows and columns, then draws the grid cells and checks if there should be a square in each cell (according to the array) and draws a square if needed. The HTML final result looks like this: (assuming I have 1 row and 3 columns, only 2 cells should have squares)
.row { display: flex; flex-wrap: wrap; flex: 10000 1 0%; } .column { display: flex; flex-wrap: wrap; max-width: 100px; min-width: 10px; padding: 4px; border: 1px solid grey; } .square { background-color: red; width: 100%; aspect-ratio: 1/1; border-radius: 5px; }
<div class="row"> <div class="column"> <div class="square"></div> </div> <div class="column"> <div class="square"></div> </div> <div class="column"></div> </div>
The rows take up the entire width of the screen and the column sizes should be the same between all columns and change based on the number of columns on the screen (for example if I have 5 columns they should all come with a width of 100 pixels but if I have 1000 columns and they should all be 10 pixels wide).
My problem is that the padding and border radius look weird after a certain breakpoint in the column size, and I want to change their values when I hit that breakpoint. I can't use @container queries because it's still not fully supported.
If it helps, I'm using vue 2. But I think a CSS solution would be better in this case.
P粉4278776762024-04-04 14:37:48
Attempt to solve the problem described:
I made a small demo to help me better explore the conditions for achieving this scenario.
.row
element is still a Flexbox container, but its Flex item does not have border
set, but uses the outline
setting for styling.
The outline takes up no space and will "collapse" when colliding with the outline generated by another element.
So, to ensure that the layout is not affected by styling weirdness, when trying to display the borders of a Flex item, this demo relies on only 2 key aspects to render those borders:
gap between flexible items
Outline
size to cover the gap left between
element.row { gap: var(--col-gap); } .column { outline: var(--col-gap) solid gray; }
Additionally, the red dot is applied as a ::after
pseudo-element with position:absolute
, again ensuring that nothing affects the grid layout:
.column.square::after { position: absolute; content: ''; background-color: red; width: 50%; aspect-ratio: 1/1; border-radius: 100%; top: 50%; left: 50%; transform: translate(-50%, -50%); }
From there, I added a "dashboard" with position:fixed
which stays at the top of the page and allows you to control:
display: none;
will not change the grid layout it depends entirely on .column
element size set by custom variable
--col-widthAlthough we have worked hard to minimize distractions and taken all the steps needed to correctly set up the grid layout based only on the fixed size of the cells , there are still some issues Rendering issues, sometimes a general mismatch pattern in border sizes for certain lines. I should say that I only have issues with my laptop display, not my desktop monitor, so that's another factor.
I tried different parameters and crunched the numbers in the demo, taking the gaps into account as well. A good and safe layout can minimize potential problems (for example, it can also increase the border size).
I couldn't get any further than this using Flex layout.
const container = document.getElementById('container');
//draws the board
emptyElementAndFillWithColumns(container, 100);
//sets some columns randomly as .square
addRandomSquares(container);
//initializes the dashboard with the value coming from the css custom props
let columnsGap = parseInt(getCssCustomProp('col-gap'));
let columnsWidth = parseInt(getCssCustomProp('col-width'));
document.getElementById('gap').value = columnsGap;
document.getElementById('width').value = columnsWidth;
document.getElementById('width').dispatchEvent(new Event('change'));
document.getElementById('cols').value = Math.trunc(container.offsetWidth / (columnsWidth+columnsGap));
//input#width change event handler
document.getElementById('width')
.addEventListener('change', event => {
const width = parseInt(event.target.value);
const newCols = Math.trunc(container.offsetWidth / (width+columnsGap));
setCssCustomProp(container, 'col-width', `${width}px`);
document.getElementById('cols').value = newCols;
});
//input#cols change event handler
document.getElementById('cols')
.addEventListener('change', event => {
const cols = parseInt(event.target.value);
const newWidth = Math.trunc(container.offsetWidth / cols) - columnsGap;
setCssCustomProp(container, 'col-width', `${newWidth}px`);
document.getElementById('width').value = newWidth;
});
//input#gap change event handler
document.getElementById('gap')
.addEventListener('change', event => {
const gap = parseInt(event.target.value);
setCssCustomProp(container, 'col-gap', `${gap}px`);
columnsGap = gap;
});
//input#toggle-dots change event handler
document.getElementById('toggle-dots')
.addEventListener('change', event => {
container.classList.toggle('hide-dots');
});
//input#toggle-counters change event handler
document.getElementById('toggle-counters')
.addEventListener('change', event => {
container.classList.toggle('hide-counters');
});
//sets the --propName custom property at the style of target
function setCssCustomProp(target, propName, value){
target.style.setProperty(`--${propName}`, `${value}`);
}
//gets the --propName custom property value from the rule set on :root
function getCssCustomProp(propName){
const propValue =
getComputedStyle(document.documentElement).getPropertyValue(`--${propName}`);
return propValue;
}
//resets the container and appends a count number of columns
function emptyElementAndFillWithColumns(target, count){
for (i = 0; i <= count; i++) {
const column = document.createElement('div');
column.classList.add('column');
target.append(column);
}
}
//adds the square class to random .column elements in target
function addRandomSquares(target){
target.querySelectorAll('.column').forEach(column => {
if (Math.random() >= 0.5)
column.classList.add('square');
})
}
:root {
--col-width: 100px;
--col-gap: 1px;
}
*,
*::after,
*::before {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: sans-serif;
}
.row {
display: flex;
flex-wrap: wrap;
gap: var(--col-gap);
counter-reset: itemnr;
}
.column {
position: relative;
display: flex;
flex-wrap: wrap;
width: var(--col-width);
height: var(--col-width);
padding: 4px;
outline: var(--col-gap) solid gray;
}
.column.square::after {
position: absolute;
content: '';
background-color: red;
width: 50%;
aspect-ratio: 1/1;
border-radius: 100%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.dashboard {
position: fixed;
right: 1rem;
top: 2rem;
border: solid darkgray;
padding: 1em;
z-index: 100;
background: gray;
color: white;
font-weight: 600;
font-size: 1.2rem;
opacity: .9;
}
.dashboard > *{
display: grid;
grid-template-columns: 1fr auto;
width: 100%;
gap: 1em;
}
.dashboard label{
}
.dashboard input[type="number"] {
width: 5em;
cursor: pointer;
}
.dashboard input[type="checkbox"] {
width: 1rem;
line-height: 1rem;
cursor: pointer;
}
#container.hide-dots .square::after{
display: none;
}
#container.hide-counters .column::before{
display: none;
}
small{
grid-column: 1 / -1;
font-size:.8rem;
text-align: center;
width: 100%;
margin-bottom: 1rem;
}
.column::before{
position: absolute;
counter-increment: itemnr;
content: counter(itemnr);
font-size: .8rem;
z-index: 10;
font-weight: 600;
}