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>

8
  • 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. Commented Aug 24, 2017 at 20:20
  • Can you knock up a quick snippet here, to show what you have so far?. Commented Aug 24, 2017 at 20:24
  • Here's a quick example: codepen.io/anon/pen/xLJpXQ Commented Aug 24, 2017 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. Commented Aug 24, 2017 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. Commented Aug 24, 2017 at 20:40

2 Answers 2

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>

Sign up to request clarification or add additional context in comments.

Comments

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`;
}, []);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.