主内容区与侧边gutter经文编号的垂直滚动对齐及交互实现
实现方案:左侧对齐的经文编号与Hover高亮
核心思路
利用CSS绝对定位实现编号与经文开头的精准垂直对齐,通过CSS选择器完成hover高亮,结合动态DOM生成适配跨多行/共享行场景,全程仅在动态渲染环节使用JS(排版逻辑纯CSS)。
1. HTML结构设计
采用「经文块容器+编号+文本」的三层结构,支持单个经文独占一行、多个经文共享一行两种场景:
<div class="scripture-container"> <!-- 单个经文独占一行 --> <div class="verse-block"> <span class="verse-number" data-ref="创1:1">1</span> <span class="verse-text">起初神创造天地。</span> </div> <!-- 多个经文共享一行 --> <div class="verse-inline-group"> <div class="verse-block"> <span class="verse-number" data-ref="创1:2">2</span> <span class="verse-text">地是空虚混沌,渊面黑暗;神的灵运行在水面上。</span> </div> <div class="verse-block"> <span class="verse-number" data-ref="创1:3">3</span> <span class="verse-text">神说:「要有光」,就有了光。</span> </div> </div> <!-- 跨多行的经文 --> <div class="verse-block"> <span class="verse-number" data-ref="创1:4">4</span> <span class="verse-text">神看光是好的,就把光暗分开了。神称光为「昼」,称暗为「夜」。有晚上,有早晨,这是头一日。</span> </div> </div>
2. CSS实现核心效果
/* 整体容器:左侧预留编号 gutter 空间 */ .scripture-container { max-width: 800px; margin: 0 auto; padding-left: 3.5em; /* 编号区域宽度,可按需调整 */ line-height: 1.6; /* 统一行高保证对齐精度 */ } /* 经文块:作为编号的相对定位参照 */ .verse-block { position: relative; margin-bottom: 0.3em; /* 经文间垂直间距 */ } /* 共享行容器:让多个经文横向排列 */ .verse-inline-group { display: flex; gap: 1.2em; /* 经文间水平间距 */ flex-wrap: wrap; /* 空间不足时自动换行 */ } /* 经文编号:固定到左侧 gutter */ .verse-number { position: absolute; left: -3.5em; /* 与容器padding-left对应,刚好移入gutter */ top: 0; width: 3em; text-align: right; color: #666; cursor: pointer; user-select: none; /* 禁止选中编号文本 */ } /* Hover编号时高亮对应经文(纯CSS实现,无JS) */ .verse-number:hover + .verse-text { background-color: #f5f5f5; border-radius: 3px; padding: 0 0.2em; /* 优化高亮视觉效果 */ }
3. 动态生成DOM(适配数据驱动场景)
通过JS循环经文数据自动生成上述结构,支持自定义经文分组逻辑(比如短经文合并到同一行):
// 示例经文数据结构 const scriptureData = { book: '创', chapter: 1, verses: [ { number: 1, text: '起初神创造天地。' }, { number: 2, text: '地是空虚混沌,渊面黑暗;神的灵运行在水面上。' }, { number: 3, text: '神说:「要有光」,就有了光。' }, { number: 4, text: '神看光是好的,就把光暗分开了。神称光为「昼」,称暗为「夜」。有晚上,有早晨,这是头一日。' } ] }; // 渲染主函数 function renderScripture(data) { const container = document.querySelector('.scripture-container'); container.innerHTML = ''; let inlineGroup = []; data.verses.forEach(verse => { // 自定义分组规则:文本长度<30的经文加入共享行组 if (verse.text.length < 30) { inlineGroup.push(verse); } else { // 先渲染之前的共享行组 if (inlineGroup.length > 0) { container.appendChild(createInlineGroup(inlineGroup)); inlineGroup = []; } // 渲染单独经文 container.appendChild(createVerseBlock(verse)); } }); // 处理剩余的共享行组 if (inlineGroup.length > 0) { container.appendChild(createInlineGroup(inlineGroup)); } } // 创建单个经文块 function createVerseBlock(verse) { const block = document.createElement('div'); block.className = 'verse-block'; const number = document.createElement('span'); number.className = 'verse-number'; number.dataset.ref = `${scriptureData.book}${scriptureData.chapter}:${verse.number}`; number.textContent = verse.number; const text = document.createElement('span'); text.className = 'verse-text'; text.textContent = verse.text; block.appendChild(number); block.appendChild(text); return block; } // 创建共享行组 function createInlineGroup(verses) { const group = document.createElement('div'); group.className = 'verse-inline-group'; verses.forEach(v => group.appendChild(createVerseBlock(v))); return group; } // 执行渲染 renderScripture(scriptureData);
方案优势
- 精准对齐:编号始终与经文第一行开头垂直对齐,不受经文行数影响;
- 场景适配:完美支持跨多行经文、共享行经文的排版需求;
- 无JS高亮:纯CSS实现hover编号高亮对应经文,性能优异;
- 动态友好:完全基于数据生成DOM,无需硬编码,可灵活适配任意经卷/章节数据。
内容的提问来源于stack exchange,提问作者Scott Sauyet
相关产品推荐
相关产品推荐

