js特效大全:用原生 JavaScript 实现常见网页效果

教程4小时前发布 jdkjadmin
0 00
js特效大全:用原生 JavaScript 实现常见网页效果

js特效大全:用原生 JavaScript 实现常见网页效果

网页里的标签切换、弹窗、轮播和表单反馈,不一定需要引入框架。原生 JavaScript 配合语义化 HTML、CSS,就能实现不少常见交互。关键不只是“能动起来”:控件要能用键盘操作,状态要让辅助技术读得到,用户输入不能直接当成 HTML 插入。

本文按常见场景给出可直接使用的代码片段。每段代码都假设对应的 HTML 结构已经写在页面中;将代码放入页面底部的 <script>,或放在 DOM 加载后执行的脚本文件中。示例侧重交互逻辑,样式可以根据项目补充。

先做好三件事:选择元素、处理状态、避免重复绑定

脚本运行时,目标元素必须已经存在。将脚本放在页面末尾,或使用 defer 加载外部脚本,通常比在页面头部直接查询元素更简单。元素不存在时应提前返回或给出清楚的错误,而不是继续调用它的方法。

交互状态尽量反映在 HTML 属性中,例如用 hidden 控制内容是否显示,用 aria-expanded 告知辅助技术展开状态。不要只靠颜色或图标变化表示状态。若一段初始化代码可能执行多次,还要避免重复注册事件监听器。

1. 标签页:适合切换同一区域中的内容

标签页适用于同一页面内相互关联、用户可按需查看的内容,例如设置面板或产品信息。若内容之间需要并排比较,或不同内容应该拥有独立链接,改用普通链接或分区通常更合适。

下面示例使用按钮切换面板。按钮和面板通过 ID 关联,非当前面板用 hidden 隐藏。

<div role="tablist" aria-label="账户设置">
  <button type="button" role="tab" id="tab-profile"
    aria-controls="panel-profile" aria-selected="true">个人资料</button>
  <button type="button" role="tab" id="tab-security"
    aria-controls="panel-security" aria-selected="false">安全设置</button>
</div>
<section role="tabpanel" id="panel-profile"
  aria-labelledby="tab-profile">个人资料内容</section>
<section role="tabpanel" id="panel-security"
  aria-labelledby="tab-security" hidden>安全设置内容</section>

<script>
const tabs = [...document.querySelectorAll('[role="tab"]')];

tabs.forEach((tab) => {
  tab.addEventListener('click', () => {
    tabs.forEach((item) => {
      const selected = item === tab;
      item.setAttribute('aria-selected', String(selected));
      item.tabIndex = selected ? 0 : -1;
      document.getElementById(item.getAttribute('aria-controls')).hidden =
        !selected;
    });
  });
});
</script>

如果要完整实现 ARIA 标签页键盘规范,还需处理左右方向键、Home、End 等操作,并把焦点移到对应标签。上面的示例支持鼠标和键盘 Tab 导航,但方向键行为没有实现;不要仅因加了 role="tab" 就认为键盘交互已经完整。

2. 手风琴:展开或收起分组内容

手风琴适合常见问题、设置项等分组信息。按钮应负责切换,内容区域则关联按钮。下面允许多个面板同时展开;如果产品要求“同一时间只能打开一个”,需要在点击时额外关闭其他面板。

<h3>
  <button type="button" class="accordion-trigger"
    aria-expanded="false" aria-controls="faq-answer">
    如何修改邮箱?
  </button>
</h3>
<div id="faq-answer" hidden>
  <p>在账户设置中更新邮箱,并按页面提示完成验证。</p>
</div>

<script>
document.querySelectorAll('.accordion-trigger').forEach((button) => {
  button.addEventListener('click', () => {
    const panel = document.getElementById(button.getAttribute('aria-controls'));
    const expanded = button.getAttribute('aria-expanded') === 'true';
    button.setAttribute('aria-expanded', String(!expanded));
    panel.hidden = expanded;
  });
});
</script>

常见错误是用普通 div 充当按钮,却没有补齐键盘操作和焦点行为。优先使用原生 button。如果面板内容有较复杂的展开动画,动画结束后也要保证视觉状态、hidden 状态和 aria-expanded 一致。

3. 对话框:优先使用原生 dialog

确认删除、查看简短详情等场景可以使用模态对话框。现代浏览器支持原生 <dialog>:调用 showModal() 会将其作为模态对话框打开,通常也能处理 Escape 关闭和焦点管理。项目仍应测试目标浏览器及实际键盘体验。

<button type="button" id="open-dialog">打开说明</button>
<dialog id="info-dialog" aria-labelledby="dialog-title">
  <h2 id="dialog-title">操作说明</h2>
  <p>请确认页面内容后继续。</p>
  <button type="button" id="close-dialog">关闭</button>
</dialog>

<script>
const dialog = document.getElementById('info-dialog');

document.getElementById('open-dialog').addEventListener('click', () => {
  if (!dialog.open) dialog.showModal();
});

document.getElementById('close-dialog').addEventListener('click', () => {
  dialog.close();
});

dialog.addEventListener('click', (event) => {
  if (event.target === dialog) dialog.close();
});
</script>

点击关闭示例判断的是事件目标是否正好为 dialog,用于处理对话框背景区域;它不等同于在所有布局下都能精确识别视觉上的遮罩点击。如果自定义背景层或定位方式不同,应相应调整。不要用一个普通隐藏的 div 冒充模态对话框,却忽略背景内容禁用、焦点限制和关闭后的焦点归还。

4. 轮播:明确边界,不要让索引越界

轮播适合有限数量、需要逐项浏览的内容。若多张内容都必须同时被看见,静态网格往往更清楚。这个简化示例只展示上一项、下一项,不自动播放;空轮播不会执行无效操作。

<div class="carousel" aria-label="推荐内容">
  <div class="carousel-track">
    <div class="slide">第一项</div>
    <div class="slide">第二项</div>
    <div class="slide">第三项</div>
  </div>
  <button type="button" id="prev-slide" aria-label="上一项">上一项</button>
  <button type="button" id="next-slide" aria-label="下一项">下一项</button>
</div>

<script>
const track = document.querySelector('.carousel-track');
const slides = [...track.querySelectorAll('.slide')];
let index = 0;

function showSlide(nextIndex) {
  if (slides.length === 0) return;
  index = (nextIndex + slides.length) % slides.length;
  track.style.transform = `translateX(-${index * 100}%)`;
}

document.getElementById('prev-slide')
  .addEventListener('click', () => showSlide(index - 1));
document.getElementById('next-slide')
  .addEventListener('click', () => showSlide(index + 1));
</script>

要让上述位移按预期工作,轨道和每个项目还需要相应 CSS:轨道横向排列,每项占一个视口宽度,并设置溢出裁切和过渡效果。CSS 过渡时间不由 JavaScript 控制。若加入自动播放,应提供暂停方式,避免用户阅读时内容自行切换;也要尊重系统的减少动态效果偏好。

5. 回到顶部:页面较长时提供快捷入口

长文章、后台列表等页面可以提供回到顶部按钮。以下代码假设页面中已有该按钮,点击后滚动到文档顶部。behavior: "smooth" 会请求平滑滚动,但用户的系统偏好和浏览器实现可能影响动态效果。

<button type="button" id="back-to-top">回到顶部</button>

<script>
document.getElementById('back-to-top').addEventListener('click', () => {
  const reduceMotion = window.matchMedia(
    '(prefers-reduced-motion: reduce)'
  ).matches;

  window.scrollTo({
    top: 0,
    behavior: reduceMotion ? 'instant' : 'smooth'
  });
});
</script>

注意,instant 并非所有浏览器都支持为 ScrollBehavior 提供相同表现。若项目需要更广泛兼容,减少动态效果时可以直接设置 document.documentElement.scrollTop = 0,或通过样式策略控制滚动行为。不要在页面滚动事件中高频执行复杂计算;如要根据滚动位置显示按钮,可以配合节流或 IntersectionObserver。

6. 表单校验:用浏览器约束做基础,用服务端做最终校验

必填、邮箱格式和长度等基础限制可以通过 HTML 属性表达,让浏览器参与校验。自定义反馈可用 setCustomValidity() 设置,再调用 reportValidity() 展示。前端校验的作用是改善体验,不是安全边界:提交到服务器的数据仍必须由服务端验证。

<form id="signup-form">
  <label for="email">邮箱</label>
  <input id="email" name="email" type="email" required>

  <label for="password">密码</label>
  <input id="password" name="password" type="password"
    minlength="12" required>

  <button type="submit">注册</button>
  <p id="form-message" aria-live="polite"></p>
</form>

<script>
const form = document.getElementById('signup-form');
const password = document.getElementById('password');
const message = document.getElementById('form-message');

password.addEventListener('input', () => {
  password.setCustomValidity(
    password.value.length < 12 ? '密码至少需要 12 个字符。' : ''
  );
});

form.addEventListener('submit', (event) => {
  event.preventDefault();

  if (!form.reportValidity()) return;

  message.textContent = '基础校验通过,请继续提交到服务器。';
});
</script>

minlength 的计数和密码策略应结合产品要求设置;长度要求本身不等于安全密码方案。示例只展示浏览器侧反馈,没有实现注册请求、密码存储或身份验证。不要在前端把密码写入日志、页面提示或本地存储。

7. 搜索输入防抖:减少连续输入触发的请求

用户快速输入时,每敲一个字符都立即搜索,可能造成多余请求。防抖会等输入暂停一段时间再执行,适合搜索建议等场景。下面代码使用文本节点安全地展示查询词;实际项目应将请求发送到服务端,并处理失败、加载状态和过时结果。

<label for="search">搜索</label>
<input id="search" type="search">
<p id="search-result" aria-live="polite"></p>

<script>
const input = document.getElementById('search');
const result = document.getElementById('search-result');
let timer;

input.addEventListener('input', () => {
  clearTimeout(timer);

  const query = input.value.trim();
  timer = setTimeout(() => {
    if (!query) {
      result.textContent = '';
      return;
    }
    result.textContent = `正在搜索:${query}`;
  }, 300);
});
</script>

这里的 300 毫秒是示例等待时间,不是浏览器限制,也不是所有业务的最佳值。请求型搜索还要考虑竞态:较早发出的请求可能较晚返回,并覆盖新结果。可使用 AbortController 取消旧请求,或用递增序号只接受最新请求的结果。渲染用户输入时不要使用 innerHTML;textContent 会把内容作为文本,而不是 HTML 解析。

8. 图片懒加载:延后加载视口外的图片

页面图片较多时,原生懒加载可以减少初始阶段不必要的图片加载。直接给图片设置 loading="lazy" 是最简方式。对需要自定义加载逻辑的场景,可以使用 IntersectionObserver 观察元素进入视口;它会在元素与观察区域相交时通知脚本,但具体触发时机受浏览器调度影响。

<img class="lazy-image" data-src="/images/item-a.jpg"
  alt="商品外观" width="640" height="400">

<script>
const images = document.querySelectorAll('.lazy-image');

if ('IntersectionObserver' in window) {
  const observer = new IntersectionObserver((entries, currentObserver) => {
    entries.forEach((entry) => {
      if (!entry.isIntersecting) return;

      const image = entry.target;
      image.src = image.dataset.src;
      image.removeAttribute('data-src');
      currentObserver.unobserve(image);
    });
  }, { rootMargin: '200px' });

  images.forEach((image) => observer.observe(image));
} else {
  images.forEach((image) => {
    image.src = image.dataset.src;
  });
}
</script>

懒加载图片应设置合适的宽高或宽高比,减少图片加载后页面跳动。data-src 的地址需要来自可信配置或经过校验;不要把不可信输入直接用作资源 URL。对于首屏关键图片,通常不应为了统一而强行延迟加载,优先保证首屏内容及时显示。

常见错误与检查清单

  • 只实现鼠标行为:按钮用原生元素,检查 Tab、Enter、Space、Escape 等键盘操作是否符合控件预期。
  • 状态不同步:显示状态、hidden、aria-expanded 等属性应保持一致,避免读屏用户得到错误信息。
  • 直接拼接用户输入:需要展示纯文本时使用 textContent。如果确实需要渲染 HTML,必须采用适当的 HTML 清理策略;普通字符串转义不能替代完整的安全审查。
  • 把前端校验当成安全措施:浏览器端代码可以被绕过。权限检查、输入校验和关键业务规则必须在服务端再次执行。
  • 没有处理空集合和重复初始化:轮播、标签等组件在没有对应元素时不应抛错;组件初始化多次时也不应重复绑定监听器。
  • 忽略不同环境:新 API 应检查支持情况并设计降级方案;动画、滚动和交互还要在真实目标浏览器、键盘和辅助技术环境中测试。

结语

原生 JavaScript 的网页特效并不复杂,真正容易遗漏的是状态管理、边界处理和安全责任。先选用合适的语义化元素,再明确控件的打开、关闭、选中或加载状态,最后补齐键盘操作、错误处理和服务端校验。把这些基础做好,简单效果才能在真实页面中稳定工作。

© 版权声明

相关文章