这是一款简洁的响应式 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>
使用说明
- 复制完整代码并保存为
index.html。 - 修改品牌名称、文案、主题色和表单字段,适配自己的项目。
- 将 JavaScript 中的演示提交逻辑替换为真实的登录、注册接口。
- 生产环境请在服务端再次校验用户输入,并使用 HTTPS 保护账号信息。
说明:本文源码为纯前端页面模板,不包含用户数据存储、账号认证和真实第三方登录能力。
© 版权声明
内容来自互联网,若有侵权请告知
THE END
