You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

打字机效果导致下方容器尺寸动态变化问题求助

解决打字机效果导致下方容器尺寸变化的问题

我实现了一个打字机效果,文本会逐字“键入”屏幕、回退,再切换显示不同文本。效果运行正常,但会导致其下方所有容器的尺寸随打字机文本容器一同变化。我尝试将打字机部分包裹在独立容器中并设置宽度,但后续容器仍会随之resize。相关代码如下:

原HTML代码

<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
  href="https://fonts.googleapis.com/css2?family=Hind+Madurai:wght@300&family=Poppins:ital,wght@0,600;1,600&family=Signika:wght@300;400&display=swap"
  rel="stylesheet"
/>
<div class="skip-link">
  <a class="visually-hidden" href="#main-content">Skip to Main Content</a>
</div>
<header>
  <h2>Name</h2>
  <nav>
    <ul>
      <li><a href="#">Welcome</a></li>
      <li><a href="#">Selected Work</a></li>
      <li><a href="#">Resume</a></li>
    </ul>
  </nav>
</header>
<main id="main-content" class="container">
  <div class="content">
    <div class="typewriter-container">
      <h1>
        I'm a <span class="typewriter"></span
        ><span class="typewriter-bar"></span>
      </h1>
    </div>
    <h2>Hi there!</h2>
  </div>
</main>
<footer></footer>

原CSS代码

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  padding: 0;
  font-family: 'Hind Madurai', Arial, Helvetica, sans-serif;
}

h1 {
  font-family: 'Poppins', Arial, Helvetica, sans-serif;
  font-size: 10vh;
  line-height: 1;
}

h2 {
  font-family: 'Signika', Arial, Helvetica, sans-serif;
}

header {
  background-color: white;
  color: #222;
  padding: 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

header h2 {
  margin: 0;
  font-weight: 300;
}

nav ul {
  list-style: none;
  padding: 0;
  margin: 0;
  text-align: right;
}

nav li {
  display: inline-block;
  margin-right: 20px;
}

nav a {
  text-decoration: none;
  color: #222;
  transition: color 0.3s ease;
}

nav a:hover {
  color: #3ca6a6;
  text-decoration: underline;
}

@media only screen and (max-width: 600px) {
  header {
    flex-direction: column;
    text-align: center;
  }
  nav {
    margin-top: 10px;
  }
  nav ul {
    flex-direction: column;
    text-align: center;
  }
  nav li {
    display: block;
    margin: 10px 0;
  }
}

.container {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  position: relative;
  margin: 0;
  padding: 0;
}

.content {
  flex: 1;
}

footer {
  background-color: #026773;
  color: white;
  text-align: center;
  padding: 10px;
  position: relative;
  z-index: 1;
  margin-top: auto;
}

footer::before {
  content: "";
  position: absolute;
  bottom: 100%;
  left: 0;
  right: 0;
  height: 400px;
  background: url(imgs/layered-peaks-haikei.svg) repeat-x;
  z-index: 2;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  border: 0;
}
.skip-link {
  position: fixed;
  top: 0;
  left: 0;
  background: #fff;
  padding: 10px;
  z-index: 999;
}

.typewriter {
  overflow: hidden;
  max-width: 800px;
  margin: 0 auto;
}

.typewriter-bar {
  border-right: 1px solid #222;
}

原JS代码

document.addEventListener("DOMContentLoaded", function () {
  var phrases = [
    "phrase 1",
    "phrase 2",
    "phrase 3",
    "phrase 4",
  ];

  var speed = 50;
  var flickerSpeed = 600;
  var pauseBetweenPhrases = 2000;

  var container = document.querySelector(".typewriter");
  var bar = document.querySelector(".typewriter-bar");
  var currentPhraseIndex = 0;
  var charIndex = 0;

  function typeWriter() {
    if (charIndex < phrases[currentPhraseIndex].length) {
      container.innerHTML +=
        phrases[currentPhraseIndex].charAt(charIndex);
      charIndex++;
      setTimeout(typeWriter, speed);
    } else {
      setTimeout(function () {
        backspace();
      }, pauseBetweenPhrases);
    }
  }

  function backspace() {
    if (charIndex >= 0) {
      container.innerHTML = phrases[currentPhraseIndex].substring(
        0,
        charIndex
      );
      charIndex--;
      setTimeout(backspace, speed);
    } else {
      currentPhraseIndex = (currentPhraseIndex + 1) % phrases.length;
      setTimeout(typeWriter, speed);
    }
  }

  function flicker() {
    bar.style.visibility =
      bar.style.visibility === "visible" ? "hidden" : "visible";
    setTimeout(flicker, flickerSpeed);
  }

  flicker();
  typeWriter(); // Start the typewriter effect
});

问题核心

打字机容器的宽度随内容动态变化,导致父元素和下方元素的布局被拉扯。即使包裹了容器,若未设置固定宽度/最小宽度,容器仍会自适应文本长度,引发布局波动。

解决方案

1. 给打字机容器设置固定宽度

先找到所有短语中最长的那一句,根据字体大小计算其实际宽度,给.typewriter-container设置固定宽度,确保容器尺寸稳定:

.typewriter-container {
  /* 示例值,根据你的最长短语实际宽度调整 */
  width: 450px;
  text-align: center;
}

.typewriter {
  display: inline-block;
  overflow: hidden;
  white-space: nowrap;
  width: 100%; /* 继承父容器的固定宽度 */
  vertical-align: bottom; /* 确保光标和文本对齐 */
}

2. 用CSS伪元素替代JS光标动画

移除独立的.typewriter-bar元素,改用伪元素实现光标,减少JS操作同时避免光标错位:

<!-- 修改后的h1结构 -->
<h1>
  I'm a <span class="typewriter"></span>
</h1>
.typewriter::after {
  content: "";
  position: absolute;
  right: 0;
  top: 0;
  height: 100%;
  width: 1px;
  background-color: #222;
  animation: flicker 0.6s infinite alternate;
}

@keyframes flicker {
  0% { opacity: 1; }
  100% { opacity: 0; }
}

/* 给.typewriter加上相对定位,让伪元素基于它定位 */
.typewriter {
  position: relative;
  display: inline-block;
  overflow: hidden;
  white-space: nowrap;
  width: 100%;
  vertical-align: bottom;
}

同时删除JS中的flicker函数及调用,用CSS动画替代。

3. 稳定父容器布局

给.content设置最小高度,避免打字机容器的微小变化影响整体布局:

.content {
  flex: 1;
  min-height: 250px; /* 根据页面内容调整 */
  text-align: center; /* 可选,让内容居中更美观 */
}

修改后的完整代码

HTML

<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
  href="https://fonts.googleapis.com/css2?family=Hind+Madurai:wght@300&family=Poppins:ital,wght@0,600;1,600&family=Signika:wght@300;400&display=swap"
  rel="stylesheet"
/>
<div class="skip-link">
  <a class="visually-hidden" href="#main-content">Skip to Main Content</a>
</div>
<header>
  <h2>Name</h2>
  <nav>
    <ul>
      <li><a href="#">Welcome</a></li>
      <li><a href="#">Selected Work</a></li>
      <li><a href="#">Resume</a></li>
    </ul>
  </nav>
</header>
<main id="main-content" class="container">
  <div class="content">
    <div class="typewriter-container">
      <h1>
        I'm a <span class="typewriter"></span>
      </h1>
    </div>
    <h2>Hi there!</h2>
  </div>
</main>
<footer></footer>

CSS

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  padding: 0;
  font-family: 'Hind Madurai', Arial, Helvetica, sans-serif;
}

h1 {
  font-family: 'Poppins', Arial, Helvetica, sans-serif;
  font-size: 10vh;
  line-height: 1;
}

h2 {
  font-family: 'Signika', Arial, Helvetica, sans-serif;
}

header {
  background-color: white;
  color: #222;
  padding: 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

header h2 {
  margin: 0;
  font-weight: 300;
}

nav ul {
  list-style: none;
  padding: 0;
  margin: 0;
  text-align: right;
}

nav li {
  display: inline-block;
  margin-right: 20px;
}

nav a {
  text-decoration: none;
  color: #222;
  transition: color 0.3s ease;
}

nav a:hover {
  color: #3ca6a6;
  text-decoration: underline;
}

@media only screen and (max-width: 600px) {
  header {
    flex-direction: column;
    text-align: center;
  }
  nav {
    margin-top: 10px;
  }
  nav ul {
    flex-direction: column;
    text-align: center;
  }
  nav li {
    display: block;
    margin: 10px 0;
  }
}

.container {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  position: relative;
  margin: 0;
  padding: 0;
}

.content {
  flex: 1;
  min-height: 250px;
  text-align: center;
}

footer {
  background-color: #026773;
  color: white;
  text-align: center;
  padding: 10px;
  position: relative;
  z-index: 1;
  margin-top: auto;
}

footer::before {
  content: "";
  position: absolute;
  bottom: 100%;
  left: 0;
  right: 0;
  height: 400px;
  background: url(imgs/layered-peaks-haikei.svg) repeat-x;
  z-index: 2;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  border: 0;
}
.skip-link {
  position: fixed;
  top: 0;
  left: 0;
  background: #fff;
  padding: 10px;
  z-index: 999;
}

.typewriter-container {
  width: 450px;
  text-align: center;
}

.typewriter {
  position: relative;
  display: inline-block;
  overflow: hidden;
  white-space: nowrap;
  width: 100%;
  vertical-align: bottom;
}

.typewriter::after {
  content: "";
  position: absolute;
  right: 0;
  top: 0;
  height: 100%;
  width: 1px;
  background-color: #222;
  animation: flicker 0.6s infinite alternate;
}

@keyframes flicker {
  0% { opacity: 1; }
  100% { opacity: 0; }
}

JS

document.addEventListener("DOMContentLoaded", function () {
  var phrases = [
    "phrase 1",
    "phrase 2",
    "phrase 3",
    "phrase 4",
  ];

  var speed = 50;
  var pauseBetweenPhrases = 2000;

  var container = document.querySelector(".typewriter");
  var currentPhraseIndex = 0;
  var charIndex = 0;

  function typeWriter() {
    if (charIndex < phrases[currentPhraseIndex].length) {
      container.innerHTML += phrases[currentPhraseIndex].charAt(charIndex);
      charIndex++;
      setTimeout(typeWriter, speed);
    } else {
      setTimeout(function () {
        backspace();
      }, pauseBetweenPhrases);
    }
  }

  function backspace() {
    if (charIndex >= 0) {
      container.innerHTML = phrases[currentPhraseIndex].substring(0, charIndex);
      charIndex--;
      setTimeout(backspace, speed);
    } else {
      currentPhraseIndex = (currentPhraseIndex + 1) % phrases.length;
      setTimeout(typeWriter, speed);
    }
  }

  typeWriter(); // Start the typewriter effect
});

内容的提问来源于stack exchange,提问作者DD1229

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 15:04:51