HTML登录+注册页面源码(响应式单文件版)

这是一款简洁的响应式 HTML 登录+注册页面源码,采用纯 HTML、CSS 和原生 JavaScript 编写,无需引入第三方框架或依赖,适合官网、后台系统、会员中心等场景使用。

页面特点

  • 登录与注册表单一键切换,包含密码显示/隐藏功能。
  • 注册表单支持邮箱、密码长度、确认密码和用户协议校验。
  • 内置密码强度提示、演示提交反馈和社交登录按钮占位。
  • 响应式布局,兼容桌面端、平板和手机屏幕。
  • 所有样式和脚本均写在单个 HTML 文件中,下载后即可修改。

完整源码

将下面代码保存为 index.html 后即可直接打开预览。登录、注册和第三方登录目前是前端演示逻辑,接入后端时请替换表单提交事件。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>星河空间 - 登录注册</title>
  <style>
    :root {
      --primary: #5b5cf0;
      --primary-dark: #4546cb;
      --ink: #20213a;
      --muted: #8588a5;
      --line: #e8e9f3;
      --surface: #ffffff;
      --soft: #f7f8ff;
      --danger: #e05263;
      --success: #24966f;
      --shadow: 0 24px 70px rgba(67, 69, 143, .16);
    }

    * { box-sizing: border-box; }

    body {
      min-height: 100vh;
      margin: 0;
      color: var(--ink);
      font: 15px/1.6 "Inter", "PingFang SC", "Microsoft YaHei", sans-serif;
      background: #eef0ff;
    }

    button,
    input { font: inherit; }

    button { cursor: pointer; }

    .auth-shell {
      display: grid;
      grid-template-columns: minmax(280px, .95fr) minmax(380px, 1.05fr);
      width: min(1080px, calc(100% - 40px));
      min-height: 680px;
      margin: 40px auto;
      overflow: hidden;
      border: 1px solid rgba(255, 255, 255, .8);
      border-radius: 28px;
      background: var(--surface);
      box-shadow: var(--shadow);
    }

    .brand-panel {
      position: relative;
      display: flex;
      flex-direction: column;
      justify-content: space-between;
      overflow: hidden;
      padding: 58px 52px;
      color: #fff;
      background:
        radial-gradient(circle at 18% 20%, rgba(255, 255, 255, .28), transparent 28%),
        linear-gradient(145deg, #6568f7 0%, #7b5be7 48%, #b45bd3 100%);
    }

    .brand-panel::before,
    .brand-panel::after {
      position: absolute;
      content: "";
      border: 1px solid rgba(255, 255, 255, .18);
      border-radius: 50%;
    }

    .brand-panel::before {
      right: -120px;
      bottom: -130px;
      width: 420px;
      height: 420px;
      box-shadow: 0 0 0 44px rgba(255, 255, 255, .06), 0 0 0 88px rgba(255, 255, 255, .05);
    }

    .brand-panel::after {
      top: 30%;
      right: 20%;
      width: 110px;
      height: 110px;
      opacity: .45;
    }

    .brand-copy,
    .brand-foot { position: relative; z-index: 1; }

    .brand {
      display: inline-flex;
      align-items: center;
      gap: 12px;
      color: #fff;
      font-size: 22px;
      font-weight: 800;
      letter-spacing: .04em;
      text-decoration: none;
    }

    .brand-mark {
      display: grid;
      width: 42px;
      height: 42px;
      place-items: center;
      border-radius: 13px;
      color: var(--primary);
      background: #fff;
      box-shadow: 0 10px 22px rgba(37, 31, 100, .18);
    }

    .brand-mark::before {
      width: 17px;
      height: 17px;
      border: 5px solid currentColor;
      border-radius: 50%;
      content: "";
    }

    .brand-copy h1 {
      max-width: 400px;
      margin: 116px 0 18px;
      font-size: clamp(32px, 4vw, 48px);
      line-height: 1.18;
      letter-spacing: -.04em;
    }

    .brand-copy p {
      max-width: 390px;
      margin: 0;
      color: rgba(255, 255, 255, .78);
      font-size: 16px;
    }

    .brand-foot {
      color: rgba(255, 255, 255, .68);
      font-size: 13px;
    }

    .auth-card {
      align-self: center;
      width: min(430px, calc(100% - 72px));
      margin: auto;
      padding: 24px 0;
    }

    .auth-heading h2 {
      margin: 0 0 6px;
      font-size: 30px;
      letter-spacing: -.04em;
    }

    .auth-heading p {
      margin: 0 0 30px;
      color: var(--muted);
    }

    .auth-tabs {
      display: flex;
      gap: 24px;
      margin-bottom: 28px;
      border-bottom: 1px solid var(--line);
    }

    .auth-tab {
      position: relative;
      padding: 0 0 13px;
      border: 0;
      color: var(--muted);
      background: transparent;
      font-weight: 700;
    }

    .auth-tab::after {
      position: absolute;
      right: 0;
      bottom: -1px;
      left: 0;
      height: 3px;
      border-radius: 3px;
      background: transparent;
      content: "";
    }

    .auth-tab.is-active { color: var(--primary); }
    .auth-tab.is-active::after { background: var(--primary); }

    .auth-panel[hidden] { display: none; }

    .field { margin-bottom: 17px; }

    .field label {
      display: block;
      margin-bottom: 7px;
      color: var(--ink);
      font-size: 13px;
      font-weight: 700;
    }

    .input-wrap { position: relative; }

    .input-wrap input {
      width: 100%;
      height: 48px;
      padding: 0 44px 0 15px;
      border: 1px solid var(--line);
      border-radius: 12px;
      outline: 0;
      color: var(--ink);
      background: var(--soft);
      transition: .2s ease;
    }

    .input-wrap input:focus {
      border-color: var(--primary);
      background: #fff;
      box-shadow: 0 0 0 4px rgba(91, 92, 240, .1);
    }

    .field-icon,
    .toggle-password {
      position: absolute;
      top: 50%;
      transform: translateY(-50%);
    }

    .field-icon {
      right: 15px;
      color: #a3a6c0;
      pointer-events: none;
    }

    .toggle-password {
      right: 12px;
      padding: 5px;
      border: 0;
      color: var(--muted);
      background: transparent;
    }

    .helper,
    .agreement {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 12px;
      color: var(--muted);
      font-size: 13px;
    }

    .helper { margin: 3px 0 22px; }

    .helper label,
    .agreement label {
      display: inline-flex;
      align-items: center;
      gap: 7px;
      margin: 0;
      font-weight: 400;
    }

    input[type="checkbox"] { accent-color: var(--primary); }

    a { color: var(--primary); text-decoration: none; }
    a:hover { text-decoration: underline; }

    .submit-button {
      width: 100%;
      height: 50px;
      border: 0;
      border-radius: 12px;
      color: #fff;
      background: linear-gradient(120deg, var(--primary), #8264e8);
      font-weight: 800;
      box-shadow: 0 12px 22px rgba(91, 92, 240, .2);
      transition: transform .2s ease, box-shadow .2s ease;
    }

    .submit-button:hover {
      transform: translateY(-2px);
      box-shadow: 0 16px 28px rgba(91, 92, 240, .28);
    }

    .divider {
      display: flex;
      align-items: center;
      gap: 12px;
      margin: 24px 0 18px;
      color: #b0b2c5;
      font-size: 12px;
    }

    .divider::before,
    .divider::after {
      flex: 1;
      height: 1px;
      background: var(--line);
      content: "";
    }

    .socials {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 10px;
    }

    .social-button {
      height: 42px;
      border: 1px solid var(--line);
      border-radius: 10px;
      color: var(--muted);
      background: #fff;
      font-size: 13px;
      transition: .2s ease;
    }

    .social-button:hover {
      border-color: var(--primary);
      color: var(--primary);
      background: var(--soft);
    }

    .agreement { justify-content: flex-start; margin: 4px 0 20px; }

    .password-meter {
      display: flex;
      gap: 4px;
      margin-top: 8px;
    }

    .password-meter i {
      flex: 1;
      height: 3px;
      border-radius: 3px;
      background: var(--line);
    }

    .password-meter.is-weak i:nth-child(1) { background: var(--danger); }
    .password-meter.is-medium i:nth-child(-n+2) { background: #e8a63e; }
    .password-meter.is-strong i { background: var(--success); }

    .toast {
      min-height: 24px;
      margin: 18px 0 0;
      color: var(--success);
      font-size: 13px;
      text-align: center;
    }

    .toast.is-error { color: var(--danger); }

    @media (max-width: 760px) {
      .auth-shell {
        display: block;
        width: min(520px, calc(100% - 24px));
        min-height: auto;
        margin: 12px auto;
        border-radius: 22px;
      }

      .brand-panel { min-height: 270px; padding: 28px 26px; }
      .brand-copy h1 { margin: 55px 0 12px; font-size: 34px; }
      .brand-copy p { font-size: 14px; }
      .brand-foot { display: none; }
      .auth-card { width: calc(100% - 48px); padding: 36px 0 42px; }
    }

    @media (max-width: 420px) {
      .auth-card { width: calc(100% - 34px); }
      .socials { grid-template-columns: 1fr; }
    }
  </style>
</head>
<body>
  <main class="auth-shell">
    <section class="brand-panel" aria-label="品牌介绍">
      <div class="brand-copy">
        <a class="brand" href="#" aria-label="返回首页">
          <span class="brand-mark" aria-hidden="true"></span>
          星河空间
        </a>
        <h1>欢迎回来,继续创造精彩。</h1>
        <p>一个简洁、轻快且适配移动端的登录注册页面模板,复制后即可按项目需求进行二次开发。</p>
      </div>
      <div class="brand-foot">© 2025 星河空间 · HTML UI Template</div>
    </section>

    <section class="auth-card" aria-labelledby="authTitle">
      <div class="auth-heading">
        <h2 id="authTitle">账号中心</h2>
        <p>使用账号登录,或注册一个新的账户</p>
      </div>

      <div class="auth-tabs" role="tablist" aria-label="登录注册切换">
        <button class="auth-tab is-active" type="button" role="tab" aria-selected="true" aria-controls="signinPanel" data-tab="signin">登录</button>
        <button class="auth-tab" type="button" role="tab" aria-selected="false" aria-controls="signupPanel" data-tab="signup">注册</button>
      </div>

      <div id="signinPanel" class="auth-panel" role="tabpanel">
        <form id="signinForm" novalidate>
          <div class="field">
            <label for="signinAccount">邮箱或用户名</label>
            <div class="input-wrap">
              <input id="signinAccount" name="account" type="text" placeholder="请输入邮箱或用户名" autocomplete="username" required>
              <span class="field-icon" aria-hidden="true">@</span>
            </div>
          </div>

          <div class="field">
            <label for="signinPassword">登录密码</label>
            <div class="input-wrap">
              <input id="signinPassword" name="password" type="password" placeholder="请输入登录密码" autocomplete="current-password" minlength="6" required>
              <button class="toggle-password" type="button" aria-label="显示密码" data-target="signinPassword">显示</button>
            </div>
          </div>

          <div class="helper">
            <label><input type="checkbox" name="remember"> 记住登录状态</label>
            <a href="#" data-action="forgot">忘记密码?</a>
          </div>

          <button class="submit-button" type="submit">立即登录</button>
        </form>

        <div class="divider">其他方式登录</div>
        <div class="socials">
          <button class="social-button" type="button" data-social="微信">微信</button>
          <button class="social-button" type="button" data-social="QQ">QQ</button>
          <button class="social-button" type="button" data-social="GitHub">GitHub</button>
        </div>
      </div>

      <div id="signupPanel" class="auth-panel" role="tabpanel" hidden>
        <form id="signupForm" novalidate>
          <div class="field">
            <label for="signupName">用户名</label>
            <div class="input-wrap">
              <input id="signupName" name="name" type="text" placeholder="请输入用户名" autocomplete="username" minlength="2" required>
              <span class="field-icon" aria-hidden="true">◎</span>
            </div>
          </div>

          <div class="field">
            <label for="signupEmail">邮箱地址</label>
            <div class="input-wrap">
              <input id="signupEmail" name="email" type="email" placeholder="name@example.com" autocomplete="email" required>
              <span class="field-icon" aria-hidden="true">@</span>
            </div>
          </div>

          <div class="field">
            <label for="signupPassword">设置密码</label>
            <div class="input-wrap">
              <input id="signupPassword" name="password" type="password" placeholder="至少 6 位字符" autocomplete="new-password" minlength="6" required>
              <button class="toggle-password" type="button" aria-label="显示密码" data-target="signupPassword">显示</button>
            </div>
            <div id="passwordMeter" class="password-meter" aria-label="密码强度">
              <i></i><i></i><i></i>
            </div>
          </div>

          <div class="field">
            <label for="signupConfirm">确认密码</label>
            <div class="input-wrap">
              <input id="signupConfirm" name="confirm" type="password" placeholder="请再次输入密码" autocomplete="new-password" minlength="6" required>
              <button class="toggle-password" type="button" aria-label="显示密码" data-target="signupConfirm">显示</button>
            </div>
          </div>

          <div class="agreement">
            <label><input type="checkbox" name="agreement" required> 我已阅读并同意<a href="#">用户协议</a></label>
          </div>

          <button class="submit-button" type="submit">创建账号</button>
        </form>
      </div>

      <p id="toast" class="toast" role="status" aria-live="polite"></p>
    </section>
  </main>

  <script>
    const tabs = document.querySelectorAll('[data-tab]');
    const panels = document.querySelectorAll('[data-panel], .auth-panel');
    const toast = document.querySelector('#toast');

    function showToast(message, isError = false) {
      toast.textContent = message;
      toast.classList.toggle('is-error', isError);
    }

    function switchTab(name) {
      tabs.forEach((tab) => {
        const active = tab.dataset.tab === name;
        tab.classList.toggle('is-active', active);
        tab.setAttribute('aria-selected', active ? 'true' : 'false');
      });

      document.querySelector('#signinPanel').hidden = name !== 'signin';
      document.querySelector('#signupPanel').hidden = name !== 'signup';
      document.querySelector('#authTitle').textContent = name === 'signin' ? '账号中心' : '创建新账号';
      showToast('');
    }

    tabs.forEach((tab) => tab.addEventListener('click', () => switchTab(tab.dataset.tab)));

    document.querySelectorAll('.toggle-password').forEach((button) => {
      button.addEventListener('click', () => {
        const input = document.querySelector(`#${button.dataset.target}`);
        const visible = input.type === 'text';
        input.type = visible ? 'password' : 'text';
        button.textContent = visible ? '显示' : '隐藏';
        button.setAttribute('aria-label', visible ? '显示密码' : '隐藏密码');
      });
    });

    const signupPassword = document.querySelector('#signupPassword');
    const passwordMeter = document.querySelector('#passwordMeter');

    signupPassword.addEventListener('input', () => {
      const value = signupPassword.value;
      const score = [
        value.length >= 6,
        /[A-Z]/.test(value) && /[a-z]/.test(value),
        /d/.test(value) && /[^A-Za-z0-9]/.test(value)
      ].filter(Boolean).length;

      passwordMeter.className = 'password-meter';
      if (score === 1) passwordMeter.classList.add('is-weak');
      if (score === 2) passwordMeter.classList.add('is-medium');
      if (score === 3) passwordMeter.classList.add('is-strong');
    });

    document.querySelector('#signinForm').addEventListener('submit', (event) => {
      event.preventDefault();
      if (!event.currentTarget.checkValidity()) {
        showToast('请完整填写登录信息', true);
        event.currentTarget.reportValidity();
        return;
      }
      showToast('演示提交成功,请接入实际登录接口');
    });

    document.querySelector('#signupForm').addEventListener('submit', (event) => {
      event.preventDefault();
      const form = event.currentTarget;
      const password = document.querySelector('#signupPassword').value;
      const confirm = document.querySelector('#signupConfirm').value;

      if (!form.checkValidity()) {
        showToast('请完整填写注册信息', true);
        form.reportValidity();
        return;
      }
      if (password !== confirm) {
        showToast('两次输入的密码不一致', true);
        document.querySelector('#signupConfirm').focus();
        return;
      }
      showToast('演示注册成功,请接入实际注册接口');
    });

    document.querySelector('[data-action="forgot"]').addEventListener('click', (event) => {
      event.preventDefault();
      showToast('请在这里接入找回密码页面或接口');
    });

    document.querySelectorAll('[data-social]').forEach((button) => {
      button.addEventListener('click', () => showToast(`${button.dataset.social}登录需要接入对应开放平台`, true));
    });
  </script>
</body>
</html>

使用说明

  1. 复制完整代码并保存为 index.html
  2. 修改品牌名称、文案、主题色和表单字段,适配自己的项目。
  3. 将 JavaScript 中的演示提交逻辑替换为真实的登录、注册接口。
  4. 生产环境请在服务端再次校验用户输入,并使用 HTTPS 保护账号信息。

说明:本文源码为纯前端页面模板,不包含用户数据存储、账号认证和真实第三方登录能力。

© 版权声明
THE END
喜欢就支持一下吧