Reputation: 137
I have a grid that its some cells do not have an element. Some cells are empty. It asked here but not working for the grid that has some empty cells My code example is below. How to get the position of an element on clicking?
#GridContainer {
width: 65vw;
height: 80vh;
margin: 8vh auto 0 auto;
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(3, 1fr);
grid-gap: 10px;
}
.column1 {
grid-column-start: 2;
}
.item {
background-color: red;
}
.column0 {
grid-column-start: 1;
}
<div id="GridContainer">
<div class="item row1 column1"></div>
<div class="item row1 column2"></div>
<div class="item row1 column3"></div>
<div class="item row2 column0"></div>
<div class="item row2 column1"></div>
<div class="item row2 column2"></div>
<div class="item row2 column3"></div>
<div class="item row3 column1"></div>
<div class="item row3 column2"></div>
<div class="item row3 column3"></div>
<div class="item row3 column4"></div>
</div>
Upvotes: 3
Views: 1007
Reputation: 20934
This snippet parses the computed styles of the grid container to determine the amount and size of the rows and columns. It creates two arrays of top left row and column positions.
It then checks for a given element if the offsetTop
and offsetLeft
of a given element is within the coordinates of each row and column. With it it determines the position on the grid.
It returns an object with the row
, column
, x
and y
values. All values are in relation to the grid element.
const container = document.getElementById('GridContainer');
const getPositionInGrid = (element, grid) => {
const gridStyles = getComputedStyle(grid);
const display = gridStyles.getPropertyValue('display');
if (display !== 'grid') {
throw new Error('grid element does not have display value of grid;');
}
const rows = gridStyles.getPropertyValue('grid-template-rows').split(' ');
const columns = gridStyles.getPropertyValue('grid-template-columns').split(' ');
const gap = parseInt(gridStyles.getPropertyValue('grid-gap'));
const rowPositions = rows.map((row, i) => {
const rowPosition = parseInt(row);
return !i ? 0 : (rowPosition + gap) * i;
});
const columnPositions = columns.map((column, i) => {
const columnPosition = parseInt(column);
return !i ? 0 : (columnPosition + gap) * i;
});
const {
offsetTop,
offsetLeft
} = element;
let row = null;
rowPositions.forEach((position, i) => {
const nextRow = rowPositions[i + 1];
if ((!nextRow && row === null) || (offsetTop >= position && offsetTop < nextRow)) {
row = i + 1;
}
});
let column = null;
columnPositions.forEach((position, i) => {
const nextColumn = columnPositions[i + 1];
if ((!nextColumn && column === null) || (offsetLeft >= position && offsetLeft < nextColumn)) {
column = i + 1;
}
});
return { row, column, y: offsetTop, x: offsetLeft};
};
container.addEventListener('click', event => {
const {
target
} = event;
if (target.classList.contains('item')) {
const {
row,
column,
x,
y
} = getPositionInGrid(target, container);
console.log('Row:', `${row},`, 'Column:', column, '|', 'x:', `${x},`, 'y:', y);
}
});
#GridContainer {
width: 65vw;
height: 80vh;
margin: 8vh auto 0 auto;
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(3, 1fr);
grid-gap: 10px;
position: relative;
}
.column1 {
grid-column-start: 2;
}
.item {
background-color: red;
}
.column0 {
grid-column-start: 1;
}
<div id="GridContainer">
<div class="item row1 column1"></div>
<div class="item row1 column2"></div>
<div class="item row1 column3"></div>
<div class="item row2 column0"></div>
<div class="item row2 column1"></div>
<div class="item row2 column2"></div>
<div class="item row2 column3"></div>
<div class="item row3 column1"></div>
<div class="item row3 column2"></div>
<div class="item row3 column3"></div>
<div class="item row3 column4"></div>
</div>
Upvotes: 1
Reputation: 137
Terbiy answer is good. However empty cells are clickable and he added eventlistener to grid not children of grid. The following code is what I want exactly. Remember that column and row number start 0 because first column offsetLeft is equal to grid offsetLeft and first row offsetTop is equal to grid offsetTop
function getPosition(el) {
var xPos = 0
var yPos = 0
while (el) {
if (el.tagName === 'BODY') {
// deal with browser quirks with body/window/document and page scroll
var xScroll = el.scrollLeft || document.documentElement.scrollLeft
var yScroll = el.scrollTop || document.documentElement.scrollTop
xPos += el.offsetLeft - xScroll + el.clientLeft
yPos += el.offsetTop - yScroll + el.clientTop
} else {
// for all other non-BODY elements
xPos += el.offsetLeft - el.scrollLeft + el.clientLeft
yPos += el.offsetTop - el.scrollTop + el.clientTop
}
el = el.offsetParent
}
return {
x: xPos,
y: yPos,
}
}
function getSquareDimensions() {
const grid = document.getElementById('GridContainer')
const gridPosition = getPosition(grid)
const rowCount = 3 //row count in grid
const columnCount = 5 //column count in grid
const width = grid.offsetWidth / columnCount
const height = grid.offsetHeight / rowCount
return { width, height }
}
function getGridElementPosition(element, gridId) {
const grid = document.getElementById(gridId)
const gridPosition = getPosition(grid)
console.log('grid position')
console.log(gridPosition)
const elementPosition = getPosition(element)
console.log('element position')
console.log(elementPosition)
const x = elementPosition.x - gridPosition.x
const y = elementPosition.y - gridPosition.y
const { width, height } = getSquareDimensions()
const column = Math.round(x / width)
const row = Math.round(y / height)
return { row, column }
}
const grid = document.getElementById('GridContainer')
for (const child of grid.children) {
child.addEventListener('click', (e) => {
console.log(getGridElementPosition(e.target, 'GridContainer'))
})
}
#GridContainer {
width: 65vw;
height: 80vh;
margin: 8vh auto 0 auto;
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(3, 1fr);
grid-gap: 10px;
}
.column1 {
grid-column-start: 2;
}
.item {
background-color: red;
}
.column0 {
grid-column-start: 1;
}
<div id="GridContainer">
<div class="item row0 column1"></div>
<div class="item row0 column2"></div>
<div class="item row0 column3"></div>
<div class="item row1 column0"></div>
<div class="item row1 column1"></div>
<div class="item row1 column2"></div>
<div class="item row1 column3"></div>
<div class="item row2 column1"></div>
<div class="item row2 column2"></div>
<div class="item row2 column3"></div>
<div class="item row2 column4"></div>
</div>
Upvotes: 0
Reputation: 690
It depends on what you assume when talking about the position. Here is my possible solution where I work with two different variants.
function handleClickedCell(cell) {
// To get the position in terms of classes.
// E.g., [ "item", "row1", "column1" ].
console.log(Array.from(cell.classList));
// To get the position in terms of pixels.
/* E.g.,
* {
* "x": 184.23333740234375,
* "y": 41.91667175292969,
* "width": 69.66665649414062,
* "height": 133.06666564941406,
* "top": 41.91667175292969,
* "right": 253.89999389648438,
* "bottom": 174.98333740234375,
* "left": 184.23333740234375
* }
*/
console.log(JSON.stringify(cell.getBoundingClientRect()));
// Other necessary actions.
}
document
.getElementById('GridContainer')
.addEventListener('click', (event) => {
handleClickedCell(event.target);
});
#GridContainer {
width: 65vw;
height: 80vh;
margin: 8vh auto 0 auto;
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(3, 1fr);
grid-gap: 10px;
}
.column1 {
grid-column-start: 2;
}
.item {
background-color: red;
}
.column0 {
grid-column-start: 1;
}
<div id="GridContainer">
<div class="item row1 column1"></div>
<div class="item row1 column2"></div>
<div class="item row1 column3"></div>
<div class="item row2 column0"></div>
<div class="item row2 column1"></div>
<div class="item row2 column2"></div>
<div class="item row2 column3"></div>
<div class="item row3 column1"></div>
<div class="item row3 column2"></div>
<div class="item row3 column3"></div>
<div class="item row3 column4"></div>
</div>
Upvotes: 1
Reputation: 536
Use this layout instead:
<div id="grid-container">
<div class="column column-0">
<div class="row row-0"></div>
<div class="row row-1 row-empty"></div>
<div class="row row-2"></div>
<div class="row row-3"></div>
<div class="row row-4 row-empty"></div>
</div>
<div class="column column-1">
<div class="row row-0"></div>
<div class="row row-1"></div>
<div class="row row-2 row-empty"></div>
<div class="row row-3"></div>
<div class="row row-4"></div>
</div>
<div class="column column-2">
<div class="row row-0"></div>
<div class="row row-1"></div>
<div class="row row-2 row-empty"></div>
<div class="row row-3"></div>
<div class="row row-4"></div>
</div>
<div class="column column-3">
<div class="row row-0 row-empty"></div>
<div class="row row-1"></div>
<div class="row row-2"></div>
<div class="row row-3 row-empty"></div>
<div class="row row-4"></div>
</div>
</div>
Then you can easily loop through your grid columns and rows and get 2D array of empty elements.
You can use some styles for your empty items as well to avoid some king of disorted view and keep your table cells size:
.row-empty {
opacity: 0; // Now you can't see this element
pointer-events: none; // Now you can't click on it
}
Upvotes: 1