3

If I use grid-template-columns: repeat(3, 1fr) to place a number of HTML elements into a 3-column grid, is it possible to find an element's column with JavaScript? Some of the elements span multiple rows as well, so the element's index won't necessarily match its column.

For example:

const myElement = document.querySelector('div:nth-child(5)');

myElement.style.background = '#f00';

const columnIndex = 1; // How do I find this?
console.log('myElement is in column ' + columnIndex);
.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-gap: 30px;
}

.item,
.item-large {
  padding: 30px;
  background: #3cf;
}

.item-large {
  grid-row: span 2;
}
<div class="grid">
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item-large"></div>
  <div class="item">Which column am I in?</div>
  <div class="item-large"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
</div>
Thomas Higginbotham
  • 1,662
  • 20
  • 25
  • I've tried `myElement.style.gridColumnStart`, but it returns an empty string unless the style was manually set. Using `getComputedStyle(myElement).gridColumnStart` produces a similar result. – Thomas Higginbotham Aug 24 '17 at 20:20
  • Can you knock up a quick snippet here, to show what you have so far?. – Keith Aug 24 '17 at 20:24
  • Here's a quick example: https://codepen.io/anon/pen/xLJpXQ – Thomas Higginbotham Aug 24 '17 at 20:32
  • I can probably use `offsetLeft` on the elements and measure their positions to determine the column, but it seems like there should be an easier way. – Thomas Higginbotham Aug 24 '17 at 20:34
  • For SO, snippets are better. People can run them inside SO, can modify and append etc into the conversation.. I've done it this time for you. – Keith Aug 24 '17 at 20:40
  • Does this answer your question? [Is there a way to detect which CSS grid column and row an element is in using javascript?](https://stackoverflow.com/questions/61964280/is-there-a-way-to-detect-which-css-grid-column-and-row-an-element-is-in-using-ja) – clickbait Mar 22 '22 at 03:04

2 Answers2

1

What I have done here is loop through all elements, and every time the left position increases and is less than my elements position, I increase a counter to keep track of the column.

I've also modified snippet here to make a little bit more interactive. If you click the div's it re-selects and shows new column number..

var myElement = document.querySelector('div:nth-child(5)');
const allElements = document.querySelector('.grid').querySelectorAll('div');


//myElement.style.background = '#f00';
myElement.classList.add('item-found');

function showFound() {
  let maxcolpos = -1, colposCount = 0;
  
  for(elem of allElements) {
    let l = elem.getBoundingClientRect().left;
    if (l > maxcolpos) {
      maxcolpos = l;
      if (myElement.getBoundingClientRect().left > l) colposCount ++;
    }
  }

  const columnIndex = colposCount + 1; //zero based, leave +1 if you want 0 based
  myElement.innerText = 'Column = ' + columnIndex;
 }
 
 showFound();
 
 document.querySelector('.grid').addEventListener("click", function(e) {
   if (e.target && !e.target.matches(".grid")) {
     myElement.classList.remove('item-found');
     myElement.innerText = '';
     myElement = e.target;
     myElement.classList.add('item-found');
     showFound();
   }
 });
.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-gap: 30px;
}

.item,
.item-large {
  padding: 30px;
  background: #3cf;
}

.item-large {
  grid-row: span 2;
}

.item-found {
  background-color: red;
}
<div class="grid">
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item-large"></div>
  <div class="item"></div>
  <div class="item-large"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
</div>
Keith
  • 22,005
  • 2
  • 27
  • 44
0

I know this topic is quite old but for those wrestling with React and TypeScript this might be interesting.

I've made a Dashboard component, which has a nested Grid component, which has nested Widget components, which can get props for how many columns to span - and if no value is given it defaults to 1.

I didn't like the last item in a grid to 'not complete' a full block. With this code, the last item in the grid will stretch to the end of the last grid column. It calculates it's current position so if you have a grid with unknown grid items but still want a full block, this will come in handy.

And don't forget to give the grid element in the JSX the ref={gridRef} attribute :-)

const gridRef = useRef<HTMLDivElement>(null);

useLayoutEffect(() => {
    if (!gridRef.current) return;
    const widgets = gridRef.current.children as HTMLCollectionOf<HTMLDivElement>;
    const widgetsLeftPosition: number[] = [];
    const widgetsInLastRow: HTMLDivElement[] = [];

    // add offset from screenedge to array
    for (let i = 0; i < widgets.length; i++) {
        const currentWidgetLeftPosition = widgets[i].getBoundingClientRect().left;
        widgetsLeftPosition.push(currentWidgetLeftPosition);
    }

    // add elements (from rear end) to array, and
    // check if position of current element has same offset as first item, then break 
    // (which results in having only the elements of the last row in the array)
    for (let i = widgetsLeftPosition.length - 1; i >= 0; i--) {
        widgetsInLastRow.push(widgets[i]);
        if (widgetsLeftPosition[i] === widgetsLeftPosition[0]) break;
    }

    // for every element in the last row: check the gridColumnEnd value and
    // take the last character (which is a 'string-integer').
    // parse it to a normal integer, then sum up all integers and return that value
    const columnSpanStart = () => {
        let sum = 0;
        for (let i = 0; i < widgetsInLastRow.length; i++) {
            const spanString = getComputedStyle(widgetsInLastRow[i]).gridColumnEnd;
            sum += parseInt(spanString.slice(-1));
        }
        return sum;
    };

    // finally, use the returned value from columnSpanStart() as gridColumn 'start' value.
    // this will overwrite the default 'grid-column-end' style given by the widget component.
    const lastWidget = widgets[widgets.length - 1];
    lastWidget.style.gridColumn = `${columnSpanStart()} / -1`;
}, []);
Robbert
  • 1
  • 3