12

Let's say I have a 3-column CSS-Grid. Is there a way, using JavaScript, to get the grid-row and grid-column of an auto-placed element?

Example:

console.log($('#test').css('grid-row'), $('#test').css('grid-column'));
// expected output: 2 3 
// actual output: two empty strings
.grid {
  display: grid;
  grid-template-columns: repeat( 3, 1fr);
}
<div class="grid">
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div id="test"></div>
  <div></div>
  <div></div>
  <div></div>
</div>

Here is a JSFiddle of the example: https://jsfiddle.net/w4u87d2f/7/

In this example I could figure it out by counting the elements and knowing the grid has three columns:

grid-column = $('#test').index() % 3 + 1;
grid-row = Math.ceil( $('#test').index() / 3 )

But that only works for very simple grids and also means I have to consider breakpoints that change the number of columns.

Edit: This is not a duplicate of Retrieve the position (X,Y) of an HTML element, as I'm not interested in pixel coordinates but the row and column number within the CSS-Grid.

10
  • How would you handle mobile? Commented Jul 13, 2018 at 14:59
  • 1
    Why not just put that into the id and make life easy? Commented Jul 13, 2018 at 15:13
  • @RyanWilson I have added a fiddle that shows the example with a bit of added CSS to give the divs dimensions and colour. Commented Jul 13, 2018 at 15:13
  • @JayGould because in the task I'm trying to solve I don't have an id and the row number will be depended on the breakpoint. Commented Jul 13, 2018 at 15:16
  • @Rainb I'm not sure what you mean, how would I handle what on mobile? Commented Jul 13, 2018 at 15:18

4 Answers 4

10

The above answer is a great start, and uses jQuery. Here is a pure Javascript equivalent, and also implements an "offset" in case you have specified the first child element's grid column (such as in a calendar where you specify the first day of the month)

function getGridElementsPosition(index) {
  const gridEl = document.getElementById("grid");

  // our indexes are zero-based but gridColumns are 1-based, so subtract 1
  let offset = Number(window.getComputedStyle(gridEl.children[0]).gridColumnStart) - 1; 

  // if we haven't specified the first child's grid column, then there is no offset
  if (isNaN(offset)) {
    offset = 0;
  }
  const colCount = window.getComputedStyle(gridEl).gridTemplateColumns.split(" ").length;

  const rowPosition = Math.floor((index + offset) / colCount);
  const colPosition = (index + offset) % colCount;

  //Return an object with properties row and column
  return { row: rowPosition, column: colPosition };
}

function getNodeIndex(elm) {
  var c = elm.parentNode.children,
    i = 0;
  for (; i < c.length; i++) if (c[i] == elm) return i;
}

function addClickEventsToGridItems() {
  let gridItems = document.getElementsByClassName("grid-item");
  for (let i = 0; i < gridItems.length; i++) {
    gridItems[i].onclick = (e) => {
      let position = getGridElementsPosition(getNodeIndex(e.target));
      console.log(`Node position is row ${position.row}, column ${position.column}`);
    };
  }
}

addClickEventsToGridItems();

Here is a corresponding Pen that shows it in action on a calendar with a specified offset.

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

2 Comments

Thank you for the pure JS version. Didn't know about window.getComputedStyle() before and it really is a game changer here.
Great ideas here. Wondering how i can reactify this if columns and rows are built in React.
8
//Add click event for any child div of div = grid
$(document).ready(function(){
    $('.grid').on('click', 'div', function(e){
          GetGridElementsPosition($(this).index()); //Pass in the index of the clicked div
    //Relevant to its siblings, in other words if this is the 5th div in the div = grid
    });
});

function GetGridElementsPosition(index){
    //Get the css attribute grid-template-columns from the css of class grid
    //split on whitespace and get the length, this will give you how many columns
    const colCount = $('.grid').css('grid-template-columns').split(' ').length;

    const rowPosition = Math.floor(index / colCount);
    const colPosition = index % colCount;

    //Return an object with properties row and column
    return { row: rowPosition, column: colPosition } ;
}

3 Comments

@Seraphithan If this isn't what you are looking for, let me know and I'll remove this.
This looks great, thanks and I think I can even adapt it to work with IE 11 grid. Hopefully there will be a direct way to access these parameters eventually, because I think this would break if an element spanned more than one row or column, but luckily that's not an issue with my task.
@seraphithan yeah, if an element can span multiple columns or rows, I'd have to see an example and it may be possible to modify this, glad it helps you.
0

Here another solution taking into considerations columns spans

function getGridPosition(elem) {
    var gridContainer = elem.parent();
    var simpleEl = elem.get(0);
    var gridItems = gridContainer.children('div');
    const colCount = $(gridContainer).css('grid-template-columns').split(' ').length;

    var row = 0;
    var col = 0;

    gridItems.each(function(index,el) {

        var item = $(el);
        if(simpleEl==el) {
            //console.log("FOUND!")
            return false;
        }
        var gridCols  = item.css("grid-column");
        if(gridCols != undefined && gridCols.indexOf("span")>=0){
            var gridColumnParts = gridCols.split('/');
            var spanValue = parseInt(gridColumnParts[0].trim().split(' ')[1], 10);
            //console.log("spanValue: " + spanValue);
            col = col+spanValue;
        }else{
            col++;
        }
        if(col>=colCount){
            col=0;
            row++;
        }
    });

    return {
        row: row,
        col: col
    };
}

Comments

0

Incremental counting answered here depends on the grid items being in order and cannot deal with the irregular order caused by grid-auto-flow: dense and individual grid-row / grid-column.

Even if human isn't interested in pixel coordinates, script can convert them into grid indexes!

//// returnedArray[elmIdx] = {row: rowIdx, column: colIdx}
function measureGridIndexes(elms) {

  const coords = {row: [], column: []};  // The two will be very sparse arrays
  for (const [elmIdx, elm] of elms.entries()) {
    const x = elm.offsetLeft, y = elm.offsetTop;
    coords.row[y] ??= [];  // An array of element indexes at the same Y pixel coordinate
    coords.row[y].push(elmIdx);
    coords.column[x] ??= [];  // X ditto
    coords.column[x].push(elmIdx);
  }

  const gridIdxs = [];
  /// .flat() densifies a sparse array
  for (const [rowIdx, elmIdxs] of coords.row.flat(0).entries()) {
    for (const elmIdx of elmIdxs) {
      gridIdxs[elmIdx] = {row: rowIdx};
    }
  }
  for (const [colIdx, elmIdxs] of coords.column.flat(0).entries()) {
    for (const elmIdx of elmIdxs) {
      gridIdxs[elmIdx].column = colIdx;
    }
  }

  return gridIdxs;

}

Demo: https://codepen.io/phroneris/pen/RwXOJZZ

Note, however, that this code only considers integer item coordinates.

And of course, this does not address cases where the position is further customized with individual margin, position, etc.
I wish no website would do such styling, which would throw away the advantages of grid layout...

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.