322 lines
10 KiB
HTML
322 lines
10 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
|
||
<head>
|
||
<meta http-equiv="Content-Type" content="text/html" charset="utf-8">
|
||
<link rel="shortcut icon" href="favicon.ico">
|
||
<title>哔哩哔哩2233娘 Live2D 预览</title>
|
||
<meta name="description" content="哔哩哔哩客户端中的所有2233官方liv2d模型" />
|
||
<meta name="keywords" content="哔哩哔哩,bilibili,2233,live2d" />
|
||
<style>
|
||
/* ── CSS 变量 ── */
|
||
:root,
|
||
[data-theme="dark"] {
|
||
--bg-body: #18191c;
|
||
--bg-panel: #1a1d22;
|
||
--bg-btn: #23262d;
|
||
--border-main: #2a2d33;
|
||
--border-btn: #3a3d44;
|
||
--color-label: #888;
|
||
--color-btn: #ccc;
|
||
--color-title: #e0e0e0;
|
||
--color-sub: #666;
|
||
--bg-canvas: #0d0d0d;
|
||
}
|
||
|
||
[data-theme="light"] {
|
||
--bg-body: #f4f5f7;
|
||
--bg-panel: #ffffff;
|
||
--bg-btn: #f0f1f3;
|
||
--border-main: #dde0e6;
|
||
--border-btn: #c8ccd4;
|
||
--color-label: #999;
|
||
--color-btn: #444;
|
||
--color-title: #222;
|
||
--color-sub: #aaa;
|
||
--bg-canvas: #e8e8ec;
|
||
}
|
||
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
background: var(--bg-body);
|
||
font-family: "Microsoft YaHei", sans-serif;
|
||
min-height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
transition: background 0.2s;
|
||
}
|
||
|
||
h1 {
|
||
padding: 18px 24px 14px;
|
||
font-size: 18px;
|
||
color: var(--color-title);
|
||
background: var(--bg-panel);
|
||
border-bottom: 1px solid var(--border-main);
|
||
letter-spacing: 1px;
|
||
}
|
||
|
||
h1 span {
|
||
font-size: 12px;
|
||
color: var(--color-sub);
|
||
margin-left: 10px;
|
||
font-weight: normal;
|
||
}
|
||
|
||
/* ── 主布局 ── */
|
||
.main {
|
||
display: flex;
|
||
flex: 1;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* ── 左侧:预览区 ── */
|
||
.preview {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: var(--bg-canvas);
|
||
border-right: 1px solid var(--border-main);
|
||
position: relative;
|
||
min-height: 480px;
|
||
transition: background 0.2s;
|
||
}
|
||
|
||
.preview-label {
|
||
position: absolute;
|
||
top: 14px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
font-size: 13px;
|
||
color: var(--color-sub);
|
||
white-space: nowrap;
|
||
pointer-events: none;
|
||
}
|
||
|
||
canvas#live2d {
|
||
display: block;
|
||
/* 实际尺寸由 JS 控制,这里只做视觉居中 */
|
||
}
|
||
|
||
/* ── 右侧:控制区 ── */
|
||
.controls {
|
||
width: 400px;
|
||
flex-shrink: 0;
|
||
background: var(--bg-panel);
|
||
overflow-y: auto;
|
||
padding: 16px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
transition: background 0.2s;
|
||
}
|
||
|
||
.controls .label {
|
||
font-size: 11px;
|
||
color: var(--color-label);
|
||
margin-top: 8px;
|
||
margin-bottom: 2px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
|
||
.controls .label:first-child {
|
||
margin-top: 0;
|
||
}
|
||
|
||
/* 角色切换 */
|
||
.char-row {
|
||
display: flex;
|
||
gap: 8px;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.char-btn {
|
||
flex: 1;
|
||
padding: 8px 0;
|
||
border: 1px solid var(--border-btn);
|
||
border-radius: 4px;
|
||
background: var(--bg-btn);
|
||
color: var(--color-btn);
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
transition: all 0.15s;
|
||
outline: none;
|
||
}
|
||
|
||
.char-btn:hover {
|
||
border-color: #00a1d6;
|
||
color: #00a1d6;
|
||
}
|
||
|
||
.char-btn.active {
|
||
background: #00a1d6;
|
||
border-color: #00a1d6;
|
||
color: #fff;
|
||
}
|
||
|
||
.btn {
|
||
width: 100%;
|
||
padding: 7px 14px;
|
||
border: 1px solid var(--border-btn);
|
||
border-radius: 4px;
|
||
background: var(--bg-btn);
|
||
color: var(--color-btn);
|
||
font-size: 13px;
|
||
cursor: pointer;
|
||
transition: all 0.15s;
|
||
outline: none;
|
||
text-align: left;
|
||
}
|
||
|
||
.btn:hover {
|
||
border-color: #00a1d6;
|
||
color: #00a1d6;
|
||
}
|
||
|
||
.btn.active {
|
||
background: #00a1d6;
|
||
border-color: #00a1d6;
|
||
color: #fff;
|
||
}
|
||
|
||
/* ── 右下角主题切换 ── */
|
||
.theme-toggle {
|
||
position: fixed;
|
||
right: 20px;
|
||
top: 10px;
|
||
padding: 7px 14px;
|
||
border: 1px solid var(--border-btn);
|
||
border-radius: 20px;
|
||
background: var(--bg-btn);
|
||
color: var(--color-btn);
|
||
font-size: 13px;
|
||
cursor: pointer;
|
||
outline: none;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||
transition: all 0.15s;
|
||
z-index: 9999;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.theme-toggle:hover {
|
||
border-color: #00a1d6;
|
||
color: #00a1d6;
|
||
}
|
||
</style>
|
||
</head>
|
||
|
||
<body>
|
||
<h1>2233娘 Live2D 预览</h1>
|
||
<div class="main">
|
||
<!-- 左:canvas -->
|
||
<div class="preview">
|
||
<div class="preview-label" id="previewLabel">加载中...</div>
|
||
<canvas id="live2d" width="512" height="512"></canvas>
|
||
</div>
|
||
<!-- 右:控制 -->
|
||
<div class="controls" id="controls">
|
||
<span class="label">角色</span>
|
||
<div class="char-row">
|
||
<button class="char-btn active" data-char="22">22娘</button>
|
||
<button class="char-btn" data-char="33">33娘</button>
|
||
</div>
|
||
<span class="label">服装</span>
|
||
<!-- 模型按钮由 JS 渲染 -->
|
||
</div>
|
||
</div>
|
||
<button class="theme-toggle" id="themeToggle"></button>
|
||
<!-- 引入Live2D_JS -->
|
||
<script type="text/javascript" src="res/js/live2d.js"></script>
|
||
<script>
|
||
// ── 模型列表 ──────────────────────────────────────────────────────────────
|
||
const MODELS = [
|
||
{ key: 'default', label: '默认模型' },
|
||
{ key: '2016.xmas.1', label: '2016圣诞节 Ver.1' },
|
||
{ key: '2016.xmas.2', label: '2016圣诞节 Ver.2' },
|
||
{ key: '2017.newyear', label: '2017新年' },
|
||
{ key: '2017.school', label: '2017校园' },
|
||
{ key: '2017.vdays', label: '2017情人节' },
|
||
{ key: '2017.summer.normal.1', label: '2017夏季(普通版)Ver.1' },
|
||
{ key: '2017.summer.super.1', label: '2017夏季(高级版)Ver.1' },
|
||
{ key: '2017.summer.normal.2', label: '2017夏季(普通版)Ver.2' },
|
||
{ key: '2017.summer.super.2', label: '2017夏季(高级版)Ver.2' },
|
||
{ key: '2017.cba-normal', label: '2017CBA(普通版)' },
|
||
{ key: '2017.cba-super', label: '2017CBA(高级版)' },
|
||
{ key: '2017.tomo-bukatsu.low', label: '2017朋友社团活动(普通版)' },
|
||
{ key: '2017.tomo-bukatsu.high', label: '2017朋友社团活动(高级版)' },
|
||
{ key: '2017.valley', label: '2017山谷?' },
|
||
{ key: '2018.spring', label: '2018春季' },
|
||
{ key: '2018.lover', label: '2018情人节' },
|
||
{ key: '2018.bls-summer', label: '2018夏季 BLS' },
|
||
{ key: '2018.bls-winter', label: '2018冬季 BLS' },
|
||
]
|
||
let currentChar = '22'
|
||
let currentModel = 'default'
|
||
// ── 渲染模型按钮 ──────────────────────────────────────────────────────────
|
||
const controls = document.getElementById('controls')
|
||
function renderModelBtns() {
|
||
// 移除旧的模型按钮(保留角色行和 label)
|
||
controls.querySelectorAll('.btn').forEach(el => el.remove())
|
||
MODELS.forEach(m => {
|
||
const btn = document.createElement('button')
|
||
btn.className = 'btn' + (m.key === currentModel ? ' active' : '')
|
||
btn.textContent = m.label
|
||
btn.addEventListener('click', () => loadModel(currentChar, m.key))
|
||
controls.appendChild(btn)
|
||
})
|
||
}
|
||
// ── 加载模型 ──────────────────────────────────────────────────────────────
|
||
function loadModel(char, modelKey) {
|
||
currentChar = char
|
||
currentModel = modelKey
|
||
const path = `res/model/${char}/model.${modelKey}.json`
|
||
document.getElementById('previewLabel').textContent =
|
||
`${char}娘 · ${MODELS.find(m => m.key === modelKey)?.label ?? modelKey}`
|
||
// 更新按钮状态
|
||
controls.querySelectorAll('.btn').forEach((btn, i) => {
|
||
btn.classList.toggle('active', MODELS[i].key === modelKey)
|
||
})
|
||
controls.querySelectorAll('.char-btn').forEach(btn => {
|
||
btn.classList.toggle('active', btn.dataset.char === char)
|
||
})
|
||
loadlive2d('live2d', path)
|
||
}
|
||
// ── 角色切换 ──────────────────────────────────────────────────────────────
|
||
controls.querySelectorAll('.char-btn').forEach(btn => {
|
||
btn.addEventListener('click', () => loadModel(btn.dataset.char, currentModel))
|
||
})
|
||
// ── 主题切换 ──────────────────────────────────────────────────────────────
|
||
const STORAGE_KEY = 'live2d_theme'
|
||
const themeBtn = document.getElementById('themeToggle')
|
||
function applyTheme(theme) {
|
||
document.documentElement.setAttribute('data-theme', theme)
|
||
themeBtn.textContent = theme === 'dark' ? '☀️ 浅色' : '🌙 深色'
|
||
}
|
||
function getInitialTheme() {
|
||
const saved = localStorage.getItem(STORAGE_KEY)
|
||
if (saved) return saved
|
||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
||
}
|
||
applyTheme(getInitialTheme())
|
||
themeBtn.addEventListener('click', () => {
|
||
const next = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark'
|
||
applyTheme(next)
|
||
localStorage.setItem(STORAGE_KEY, next)
|
||
})
|
||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
|
||
if (!localStorage.getItem(STORAGE_KEY)) applyTheme(e.matches ? 'dark' : 'light')
|
||
})
|
||
// ── 初始化 ────────────────────────────────────────────────────────────────
|
||
renderModelBtns()
|
||
loadModel('22', 'default')
|
||
</script>
|
||
</body>
|
||
|
||
</html> |