133 lines
4.8 KiB
Markdown
133 lines
4.8 KiB
Markdown
# HTML 幻灯片结构参考
|
|
|
|
每份演示都是一个可独立打开的 HTML 文件。把 [viewport-base.css](viewport-base.css) 的完整内容放在 `<style>` 中,再加入项目自己的变量和组件样式。
|
|
|
|
## 最小结构
|
|
|
|
```html
|
|
<!doctype html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>项目演示</title>
|
|
<style>
|
|
:root {
|
|
--stage-bg: #eef3ef;
|
|
--slide-bg: #fffdf7;
|
|
--ink: #17332a;
|
|
--muted: #5c6f66;
|
|
--accent: #f08a63;
|
|
--display-font: "Trebuchet MS", "PingFang SC", sans-serif;
|
|
--body-font: "Avenir Next", "PingFang SC", sans-serif;
|
|
}
|
|
|
|
* { box-sizing: border-box; }
|
|
/* 在此处粘贴 viewport-base.css 的完整内容。 */
|
|
|
|
.slide-inner {
|
|
width: 100%;
|
|
height: 100%;
|
|
padding: 92px;
|
|
color: var(--ink);
|
|
font-family: var(--body-font);
|
|
}
|
|
|
|
.reveal {
|
|
opacity: 0;
|
|
transform: translateY(28px);
|
|
transition: opacity .55s ease, transform .55s ease;
|
|
}
|
|
|
|
.slide.visible .reveal {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.reveal:nth-child(2) { transition-delay: .08s; }
|
|
.reveal:nth-child(3) { transition-delay: .16s; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="deck-viewport" aria-label="项目演示">
|
|
<main class="deck-stage" id="deckStage">
|
|
<section class="slide title-slide active visible" aria-label="第 1 页">
|
|
<div class="slide-inner">
|
|
<h1 class="reveal">项目标题</h1>
|
|
<p class="reveal">一句话说明项目解决了什么问题</p>
|
|
</div>
|
|
</section>
|
|
<!-- 其余页使用同样的 .slide 结构。 -->
|
|
</main>
|
|
</div>
|
|
<div class="deck-controls" aria-label="演示控制">
|
|
<span id="pageCount">1 / 1</span>
|
|
</div>
|
|
<script>
|
|
class SlidePresentation {
|
|
constructor() {
|
|
this.slides = [...document.querySelectorAll('.slide')];
|
|
this.stage = document.querySelector('#deckStage');
|
|
this.pageCount = document.querySelector('#pageCount');
|
|
this.index = 0;
|
|
this.startX = null;
|
|
this.scaleStage();
|
|
window.addEventListener('resize', () => this.scaleStage());
|
|
window.addEventListener('keydown', (event) => this.onKey(event));
|
|
window.addEventListener('wheel', (event) => {
|
|
if (Math.abs(event.deltaY) > 12) this.show(this.index + Math.sign(event.deltaY));
|
|
}, { passive: true });
|
|
window.addEventListener('touchstart', (event) => {
|
|
this.startX = event.touches[0]?.clientX ?? null;
|
|
}, { passive: true });
|
|
window.addEventListener('touchend', (event) => {
|
|
if (this.startX === null) return;
|
|
const endX = event.changedTouches[0]?.clientX ?? this.startX;
|
|
if (Math.abs(endX - this.startX) > 40) this.show(this.index + (endX < this.startX ? 1 : -1));
|
|
this.startX = null;
|
|
}, { passive: true });
|
|
this.show(0);
|
|
}
|
|
|
|
scaleStage() {
|
|
const factor = Math.min(window.innerWidth / 1920, window.innerHeight / 1080);
|
|
const x = (window.innerWidth - 1920 * factor) / 2;
|
|
const y = (window.innerHeight - 1080 * factor) / 2;
|
|
this.stage.style.transform = `translate(${x}px, ${y}px) scale(${factor})`;
|
|
}
|
|
|
|
onKey(event) {
|
|
if (['ArrowRight', 'ArrowDown', 'PageDown', ' '].includes(event.key)) {
|
|
event.preventDefault(); this.show(this.index + 1);
|
|
}
|
|
if (['ArrowLeft', 'ArrowUp', 'PageUp'].includes(event.key)) {
|
|
event.preventDefault(); this.show(this.index - 1);
|
|
}
|
|
}
|
|
|
|
show(index) {
|
|
this.index = Math.max(0, Math.min(index, this.slides.length - 1));
|
|
this.slides.forEach((slide, slideIndex) => {
|
|
const active = slideIndex === this.index;
|
|
slide.classList.toggle('active', active);
|
|
slide.classList.toggle('visible', active);
|
|
slide.setAttribute('aria-hidden', String(!active));
|
|
});
|
|
if (this.pageCount) this.pageCount.textContent = `${this.index + 1} / ${this.slides.length}`;
|
|
}
|
|
}
|
|
|
|
new SlidePresentation();
|
|
</script>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
## 内容和可访问性
|
|
|
|
- 使用一个明确的 `<h1>`,后续页面使用有顺序的 `<h2>`;图片提供简短 `alt` 文本。
|
|
- 不把重要内容只放在动画、颜色或悬停状态里;对比度要足够。
|
|
- 为每个 `.slide` 加 `aria-label`,并在切换时同步 `aria-hidden`。
|
|
- 不在幻灯片内放调试按钮、文件路径、模板名称、内部提示或生成过程文字。
|
|
- 通过相对路径引用 `assets/`,不要把用户机器路径写入 HTML。
|