首页 > 解决方案 > 使文本出现在悬停更改 div 高度

问题描述

我正在构建一个站点,当您将鼠标悬停在不同的块上时,其中一个组件需要在同一位置显示不同的文本。我正在使用 jQuery 来完成此操作并更改 html,但是我注意到由于文本的大小不同,它会向下推 div 为文本分配更多空间。

是否可以保持文本透明或同时更改颜色和 html 以产生弹出的错觉?

请看下面的代码:

$(".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>

标签: javascriptjquerycss

解决方案


我解决问题的方法是基于使用 CSS Grid 来创建显示消息的元素的初始大小,并将消息添加到 HTML,而不是替换文本。

当然,您可以通过将内容放置在屏幕外以告知必要的大小来确定元素的大小,然后在屏幕上显示消息之前对这些维度进行动画处理,但这比感觉需要的工作更多。

我建议的方法如下,代码本身带有解释性注释:

// 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 小提琴演示

当然,任何可以在 jQuery 中完成的事情,也可以在原生 JavaScript 中实现;同样,解释性说明在以下代码的注释中:

// 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 小提琴演示

参考:


推荐阅读