Oskar Zanota
Oskar Zanota

Reputation: 500

Center element on window using transform

I'm trying to center a card element when it is clicked, to highlight it and separate if from the other cards, using CSS transforms. I know CSS transform is relative to the element's location, but I have to use transform and not top/left to avoid repositioning of the other cards. I've tried to calculate the correct offset using JavaScript, like so:

const cardRect = cardElement.getBoundingClientRect();

const offsetX = window.innerWidth / 2 - cardRect.x - cardRect.width / 2;
const offsetY = window.innerHeight / 2- cardRect.y - cardRect.height / 2;

However that did not work, because the bounding client rect changes all the time, as I have a scale(1.1) on card hover.

So how can I move the clicked card to the center, without offsetting all the other ones?

The cards are created like so in the HTML:

<div class='container'>

  <div class='movie'>
    <img :src='/images/1.png'>
    <h3>Harry Potter and the Sorcerer's Stone</h3>
  </div>

  <div class='movie'>
    <img :src='/images/2.png'>
    <h3>Harry Potter and the Chamber of Secrets</h3>
  </div>

  <div class='movie'>
    <img :src='/images/3.png'>
    <h3>Harry Potter and the Prisoner of Azkaban</h3>
  </div>
  
  etc...

</div>

And everything is styled like so:

.container {
  display: flex;
  flex-flow: row wrap;
}

.movie {
  width: min-content;
  flex: 1;
}

.movie:not(.selected-movie):hover { /* .selected-movie is a class applied to the clicked card */
  transform: scale(1.1);
  box-shadow: 10px 10px 32px 0px rgba(0,0,0,0.4),
              -10px -10px 32px 0px rgba(0,0,0,0.4);
}

.selected-movie {
  transform: translate(offsetX, offsetY); /* Here are the offsets needed */
  flex: 0;
  display: grid;
  grid-template-areas:
      'poster title'
      'poster description';
}

Here is a JSFiddle showing the problem: https://jsfiddle.net/u6tpbjna/5/

Upvotes: 1

Views: 850

Answers (1)

Brandon McConnell
Brandon McConnell

Reputation: 6119

Some of your other CSS styles for .selected-movie were throwing off the un-selected cards' styles when the clicked movie is selected, so I've temporarily commented out those styles. Regardless, I believe this should answer the question you're asking about the using those offsets in the CSS.

This is a great opportunity to use CSS variables, which you can set on pageload and window resize using JavaScript. From there, everything else can be done in CSS.

See below:

const cards = Array.from(document.querySelectorAll('.movie'));

const setCardOffsets = () => {
  for (card of cards) {
    const cardRect = card.getBoundingClientRect();
    const cardOffsetX = window.innerWidth / 2 - cardRect.x - cardRect.width / 2;
    const cardOffsetY = window.innerHeight / 2- cardRect.y - cardRect.height / 2;
    card.style.setProperty('--offset-x', cardOffsetX + "px");
    card.style.setProperty('--offset-y', cardOffsetY + "px");
  }
}
setCardOffsets();
window.onresize = setCardOffsets;

document.addEventListener('click', e => {
  if (e.target && e.target?.matches('.movie, .movie *')) {
    const clickedCard = e.target.matches('.movie') ? e.target : e.target.closest('.movie');
    if (clickedCard.classList.contains('selected-movie')) {
      clickedCard.classList.remove('selected-movie');
      return;
    }
    cards.forEach(card => card.classList.remove('selected-movie'));
    clickedCard.classList.add('selected-movie');
  }
});
.container {
  display: flex;
  flex-flow: row wrap;
}

.movie {
  transform: scale(1) translate(0px, 0px);
  width: min-content;
  flex: 1;
  position: relative;
  transition: all 0.2s ease-out;
}

.movie:not(.selected-movie):hover {
  transform: scale(1.1) translate(0px, 0px);
  box-shadow: 10px 10px 32px 0px rgba(0,0,0,0.4),
              -10px -10px 32px 0px rgba(0,0,0,0.4);
  z-index: 2;
}

.selected-movie {
  transform: scale(1.1) translate(var(--offset-x), var(--offset-y));
  /*flex: 0;
  display: grid;
  grid-template-areas:
      'poster title'
      'poster description';*/
  z-index: 1;
}
<div class='container'>

  <div class='movie'>
    <img src='/images/1.png'>
    <h3>Harry Potter and the Sorcerer's Stone</h3>
  </div>

  <div class='movie'>
    <img src='/images/2.png'>
    <h3>Harry Potter and the Chamber of Secrets</h3>
  </div>

  <div class='movie'>
    <img src='/images/3.png'>
    <h3>Harry Potter and the Prisoner of Azkaban</h3>
  </div>

</div>

I hope this helps! Let me know if you have any questions about my implementation of this.

Upvotes: 3

Related Questions