Damian
Damian

Reputation: 11

Making text appear on hover changes div height

I am building a site and one of the components requires different text to be displayed in the same spot when you hover over different blocks. I am using jQuery to accomplish this and changing the html, however I am noticing since the text is different sizes it pushes down the div to allocate more room for the text.

Is it possible to keep the text transparent or something so the colour and html is changed at the same time, to give the illusion it is popping in?

Please see code below:

$(".stats-text-1").hover(
  function() {
    $(".stats-text").html(
      "Our client’s monetary milestones are driven by our social tactics and digital marketing."
    );
  },
  function() {
    $(".stats-text").html(" ");
  }
);

$(".stats-text-2").hover(
  function() {
    $(".stats-text").html(
      "Our experience is from more than just a couple of wins - it’s from learning through years of wins and losses."
    );
  },
  function() {
    $(".stats-text").html(" ");
  }
);

$(".stats-text-3").hover(
  function() {
    $(".stats-text").html(
      "Our clients currently see a minimum average of 5.4 times return on ad spend."
    );
  },
  function() {
    $(".stats-text").html(" ");
  }
);
.stats-1 {
  font-size: 12vw;
  font-weight: bold;
  color: rgba(255, 255, 255, 0.83);
}

.stats-2 {
  font-size: 2vw;
  font-weight: bold;
  color: #f2f2f2;
}

.stats-3 {
  font-size: 2vw;
  color: rgba(255, 255, 255, 0.6);
}

.stats-text {
  padding-top: 1rem;
  font-size: 2vw;
  text-align: left;
  color: #fff6f4;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="row">
  <div class="col-1">&nbsp;</div>
  <div class="col-11 card-2-title">BEEN THERE, DONE THAT.</div>
  <div class="card-2-title-mobile">BEEN THERE, DONE THAT.</div>
</div>
</div>

<div class="row stats-border">
  <div class="col-1">&nbsp;</div>
  <div class="col-3 stats stats-text-1">
    <h1 class="stats-1" style="text-align: center">11</h1>
    <h2 class="stats-2" style="text-align: center">Million</h2>
    <h3 class="stats-3" style="text-align: center">
      Revenue Generated
    </h3>
  </div>
  <div class="col-spec24">&nbsp;</div>
  <div class="col-3 stats stats-text-2">
    <h1 class="stats-1" style="text-align: center">9</h1>
    <h2 class="stats-2" style="text-align: center">Years</h2>
    <h3 class="stats-3" style="text-align: center">
      In The Making
    </h3>
  </div>
  <div class="col-spec24">&nbsp;</div>
  <div class="col-3 stats stats-text-3">
    <h1 class="stats-1" style="text-align: center">6</h1>
    <h2 class="stats-2" style="text-align: center">Times</h2>
    <h3 class="stats-3" style="text-align: center">
      Return On Ad Spend
    </h3>
  </div>
  <div class="col-1">&nbsp;</div>
</div>
<div class="card-2-desktop" style="padding-bottom: 15vw">
  <div class="row">
    <div class="col-1">&nbsp;</div>
    <div class="col-7 stats-text" id="statsText"></div>
    <div class="col-4">&nbsp;</div>
  </div>
</div>

Upvotes: 0

Views: 470

Answers (1)

David Thomas
David Thomas

Reputation: 253396

The way I've adressed the problem is based on using CSS Grid to create the initial size of the element in which you're showing your messages, and also adding the messages to the HTML, rather than replacing text.

While you could – of course – establish the sizes of the elements by positioning the content off-screen to inform the necessary sizing, and then animate to those dimensions before showing the message on-screen, that's more work than feels necessary.

My suggested approach is below, with explanatory comments in the code itself:

// using the '.stats' selector to obtain a jQuery Object containing
// all of the elements with that class-name in the document,
// we then use the attr() method to set the custom data-index attribute
// for later use:
$('.stats').attr('data-index', function(i) {
  return i + 1;
  // rather than the hover() method we use the on() method instead to handle
  // both 'mouseenter' and 'mouseleave' events, and we pass the Event Object,
  // as 'evt', into the anonymous function:
}).on('mouseenter mouseleave', function(evt) {

  // here we use jQuery's data() method to retrieve the value of the
  // data-index custom-attribute:
  let index = $(this).data('index');

  // here we retrieve the .message element which has the same
  // data-index attribute and attribute-value, which is also within
  // a .marketing element:
  $(`.marketing .message[data-index="${index}"]`)
    // we then use the toggleClass() method to add, or remove,
    // the 'visible' class to the relevant .message element
    // depending on whether the assessment returns true or false;
    // if the evt.type is exactly-equal to 'mouseenter' the
    // assessment returns Boolean true, and the class is added;
    // otherwise Boolean false is returned and the class is
    // removed:
    .toggleClass('visible', evt.type === 'mouseenter');
});

$('input').on('input', function(){
  $('main').css('--textSize',`${$(this).val()}rem`)
}).change();
/* a basic CSS reset to ensure that all elements
   are sized in similar ways: */
*,
 ::before,
 ::after {
  box-sizing: border-box;
  font-size: 1rem;
  font-weight: normal;
  line-height: 1.5;
  margin: 0;
  padding: 0;
}

/* defining CSS Grid as the layout of
   the <main> element: */
main {
  display: grid;
  /* defining three equal-width columns, each
     of one fractional-unit of the available
     space: */
  grid-template-columns: repeat(3, 1fr);
  margin: 0.5em auto;
  width: 90vw;
}

/* I removed the inline <style> attribute from the
   various elements, since it made the HTML noisier
   than I'd like (adjust to taste of course): */
.stats > :is(h1, h2, h3) {
  text-align: center;
}

/* I assumed that the messages should be full-width,
   so here I defined the .marketing element should
   start in the first track and end in the last: */
.marketing {
  grid-column: 1 / -1;
}

/* again, using CSS Grid for the element that holds the
   marketing messages: */
.marketing > div {
  display: grid;
  /* defining a single named area in which the marketing
     claims should appear: */
  grid-template-areas: "claims";
}

.marketing > div > .message {
  /* here we position all of the .message elements into
     the same grid area; which allows the largest grid-item
     to define the size of that grid area: */
  grid-area: claims;
  /* effectively hiding the elements, and centring the text: */
  opacity: 0;
  pointer-events: none;
  text-align: center;
  user-select: none;
  z-index: -1;
}

/* this is the 'background' element against which the .message
    will be displayed, this can be easily adjusted or the
    .message elements themselves can have their own background: */ 
.marketing > div > .mask {
  background: linear-gradient(135deg, lime, #ffaf);
  grid-area: claims;
}

/* when the 'visible' class-name is added to the .message elements
   this CSS promotes their visibility, by raising their opacity to
   1 (fully visible), raising their z-index above the background
   and re-enabling pointer events and user-selection: */
.marketing > div > .message.visible {
  opacity: 1;
  pointer-events: auto;
  user-select: auto;
  z-index: 2;
}

.message:nth-child(2) {
  font-size: var(--textSize, inherit);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!-- this is absolutely irrelevant to the demo, but does demonstrate how the
     grid size automatically adjusts to the size of the largest element -->
<label>Adjust text-size of the second <code>message</code> element to:
  <input  type="number"
          min="0.5"
          max="20"
          step="0.5"
          value="1" /></label>

<!-- using the <main> element as a wrapping block for the posted content;  -->
<main>
  <!-- I removed the 'stats-text-n' class-name, since that would seem
       to be more use as an id (given its role in uniquely identifying
       a specific element, and also because that makes your code
       inherently non-reusable; whereas each element has a 'stats' class-
       name which allows us to generalise the JavaScript -->
  <div class="col-3 stats">
    <h1 class="stats-1">11</h1>
    <h2 class="stats-2">Million</h2>
    <h3 class="stats-3">Revenue Generated</h3>
  </div>

  <div class="col-3 stats">
    <h1 class="stats-1">9</h1>
    <h2 class="stats-2">Years</h2>
    <h3 class="stats-3">In The Making</h3>
  </div>

  <div class="col-3 stats">
    <h1 class="stats-1">6</h1>
    <h2 class="stats-2">Times</h2>
    <h3 class="stats-3">Return On Ad Spend</h3>
  </div>

  <!-- here I added the 'marketing' class-name, since the 'card-2-desktop' seems
       as though it may be a product of a framework -->
  <div class="card-2-desktop marketing">
    <div class="row">
      <!-- these messages were taken from your jQuery code, and placed inside of
           the '.row' element, along with a custom data-* attribute which indicates
           which of the '.stats' elements it refers to: -->
      <div class="message" data-index="1">Our client’s monetary milestones are driven by our social tactics and digital marketing.</div>
      <div class="message" data-index="2">Our experience is from more than just a couple of wins - it’s from learning through years of wins and losses.</div>
      <div class="message" data-index="3">Our clients currently see a minimum average of 5.4 times return on ad spend.</div>
      <!-- an element to act as the background of the other elements, this is
           entirely optional and largely irrelevant -->
      <div class="mask"></div>
    </div>
  </div>
</main>

JS Fiddle demo.

Of course, anything that can be accomplished in jQuery can also be achieved in native JavaScript; again, explanatory notes are in the comments of the code below:

// we use Array.from() to convert the NodeList returned by
// document.querySelectorAll() into an Array, in order to
// use Array methods later:
const messages = Array.from(
    // here we retrieve all .message elements within a .marketing
    // element:
    document.querySelectorAll('.marketing .message')
  ),
  // defining the toggle function, using Arrow syntax, and passing
  // the Event Object ('evt') into the function:
  toggle = (evt) => {
    // we use 'currentTarget' property of the Event Object to find
    // the element to which the event-handler was bound, as opposed
    // to the 'target' property which simply returns the element
    // upon which the event was initially fired; from that element
    // we retrieve the data-index attribute-value:
    let index = evt.currentTarget.dataset.index,
    
        // here we filter the Array of .message elements to find the
        // element(s) matching the the supplied filter, using an
        // Arrow function to pass the current Array-element into
        // the function body:
        message = messages.filter(
          // here we're looking to retain elements whose data-index
          // attribute-value matches that of the .stats element
          // upon which the event-handler was triggered:
          (msg) => msg.dataset.index === index
        );

    // Array.prototype.filter() returns an Array, so here we use
    // Array.prototype.forEach() to iterate through that Array:
    message.forEach(
      // here we toggle the 'visible' class-name on the retained
      // .message elements, if the Event-type (evt.type) is exactly
      // equal to 'mouseenter' the assessment returns Boolean true,
      // and the class-name is added; otherwise Boolean false is
      // returned and the class-name is removed (this generates no
      // error if the class-name addition or removal would match
      // the existing state):
      (msg) => msg.classList.toggle('visible', evt.type === 'mouseenter')
    );
  };
  
// here we retrieve all elements matching the supplied CSS selector,
// and use NodeList.prototype.forEach() to iterate over that NoseList:
document.querySelectorAll('.stats').forEach(
  // here we pass in a reference to the current Node of the NodeList
  // (stat) and the index of that Node in the NodeList (i):
  (stat, i) => {
    // here we set the data-index attribute to be equal to the index
    // plus 1 (to match the 1-based index in the HTML attributes I
    // added):
    stat.dataset.index = i + 1;
    
    // and then bind the toggle() function - note the deliberate
    // omission of the parentheses in the below code - as the
    // event-handler for both the 'mouseenter' and 'mouseeout'
    // events:
    stat.addEventListener('mouseenter', toggle);
    stat.addEventListener('mouseleave', toggle);
  });

// again, largely irrelevant to the demo but demonstrates how the font-size
// determines the grid-area size to avoid size jumps between 'empty' and
// 'populated':
document.querySelector('input').addEventListener('input', (evt) =>
  document.querySelectorAll('.marketing')
  .forEach(
    (el) => el.style.setProperty(
        '--textSize',
        `${evt.currentTarget.value}rem`)
  )
);
*,
::before,
::after {
  box-sizing: border-box;
  font-size: 1rem;
  font-weight: normal;
  line-height: 1.5;
  margin: 0;
  padding: 0;
}

main {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  margin: 0.5em auto;
  width: 90vw;
}


.stats > :is(h1, h2, h3) {
  text-align: center;
}


.marketing {
  grid-column: 1 / -1;
}


.marketing > div {
  display: grid;

  grid-template-areas: "claims";
}

.marketing > div > .message {
  grid-area: claims;
  opacity: 0;
  pointer-events: none;
  text-align: center;
  user-select: none;
  z-index: -1;
}

.marketing > div > .mask {
  background: linear-gradient(135deg, lime, #ffaf);
  grid-area: claims;
}

.marketing > div > .message.visible {
  opacity: 1;
  pointer-events: auto;
  user-select: auto;
  z-index: 2;
}

.message:nth-child(2) {
  font-size: var(--textSize, inherit);
}
<label>Adjust text-size of the second <code>message</code> element to:
  <input type="number" min="0.5" max="20" step="0.5" value="1" /></label>

<main>
  <div class="col-3 stats">
    <h1 class="stats-1">11</h1>
    <h2 class="stats-2">Million</h2>
    <h3 class="stats-3">Revenue Generated</h3>
  </div>

  <div class="col-3 stats">
    <h1 class="stats-1">9</h1>
    <h2 class="stats-2">Years</h2>
    <h3 class="stats-3">In The Making</h3>
  </div>

  <div class="col-3 stats">
    <h1 class="stats-1">6</h1>
    <h2 class="stats-2">Times</h2>
    <h3 class="stats-3">Return On Ad Spend</h3>
  </div>

  <div class="card-2-desktop marketing">
    <div class="row">
      <div class="message" data-index="1">Our client’s monetary milestones are driven by our social tactics and digital marketing.</div>
      <div class="message" data-index="2">Our experience is from more than just a couple of wins - it’s from learning through years of wins and losses.</div>
      <div class="message" data-index="3">Our clients currently see a minimum average of 5.4 times return on ad spend.</div>
      <div class="mask"></div>
    </div>
  </div>
</main>

JS Fiddle demo.

References:

Upvotes: 1

Related Questions