Merge pull request #1518 from CyC2018/site-load-a11y-v2

Update website loading and accessibility
This commit is contained in:
CyC2018
2026-11-01 11:38:36 +08:00
committed by GitHub
4 changed files with 64 additions and 24 deletions
+5 -3
View File
@@ -30,8 +30,10 @@ assert result.returncode == 0, result.stderr
assert digest(OUTPUT / 'asset-manifest.json') == first_manifest, '相同输入的两次构建不一致'
report = json.loads((BASE / 'dist-report.json').read_text())
url, image = next((url, path) for url, path in report['imageReplacements'].items()
if path.startswith('/notes/pics/') and path.endswith('.png'))
# 优先选 PNG;笔记的旧外链图片都已重画为本地 PNG 时,剩下的多是 GIF,用同类型的图片替换
candidates = [(url, path) for url, path in report['imageReplacements'].items() if path.startswith('/notes/pics/')]
url, image = next((item for item in candidates if item[1].endswith('.png')), candidates[0])
suffix = Path(image).suffix
image_name = unquote(image).lstrip('/')
note = next(path for path in (SOURCE / 'notes').glob('*.md') if url in path.read_text())
note_name = note.relative_to(SOURCE)
@@ -44,7 +46,7 @@ with tempfile.TemporaryDirectory(prefix='.update-check-', dir=BASE) as temp:
shutil.copytree(SOURCE, source)
with (source / note_name).open('a') as handle:
handle.write('\n\n## 本地验证更新\n\n本地同步验证示例词:这是未上传的更新测试。\n')
replacement = next(path for path in (SOURCE / 'notes/pics').glob('*.png') if digest(path) != original_image)
replacement = next(path for path in (SOURCE / 'notes/pics').glob('*' + suffix) if digest(path) != original_image)
shutil.copyfile(replacement, source / image_name)
shutil.copyfile(replacement, source / 'notes/pics/local-validation-new.png')
(source / 'notes/本地新增验证.md').write_text('# 本地新增验证\n\n仅在测试副本中生成。\n\n![新图片](pics/local-validation-new.png)\n')
+12 -12
View File
@@ -13,17 +13,17 @@
<body>
<div id="app">正在载入 CS-Notes…</div>
<noscript>请启用 JavaScript 阅读网站,或访问 <a href="https://github.com/CyC2018/CS-Notes">GitHub 仓库</a>。</noscript>
<script src="/search-paths.js"></script>
<script src="/site.js"></script>
<script src="/search.js"></script>
<script src="/vendor/docsify.min.js"></script>
<script src="/vendor/docsify-copy-code.min.js"></script>
<script src="/vendor/zoom-image.min.js"></script>
<script src="/vendor/prism-bash.min.js"></script>
<script src="/vendor/prism-c.min.js"></script>
<script src="/vendor/prism-java.min.js"></script>
<script src="/vendor/prism-sql.min.js"></script>
<script src="/vendor/prism-python.min.js"></script>
<script src="/vendor/prism-json.min.js"></script>
<script src="/search-paths.js" defer></script>
<script src="/site.js" defer></script>
<script src="/search.js" defer></script>
<script src="/vendor/docsify.min.js" defer></script>
<script src="/vendor/docsify-copy-code.min.js" defer></script>
<script src="/vendor/zoom-image.min.js" defer></script>
<script src="/vendor/prism-bash.min.js" defer></script>
<script src="/vendor/prism-c.min.js" defer></script>
<script src="/vendor/prism-java.min.js" defer></script>
<script src="/vendor/prism-sql.min.js" defer></script>
<script src="/vendor/prism-python.min.js" defer></script>
<script src="/vendor/prism-json.min.js" defer></script>
</body>
</html>
+29 -7
View File
@@ -1,5 +1,9 @@
:root { --theme-color: #369b72; }
:root { --theme-color: #2a7a5a; }
body, .sidebar, .sidebar-toggle { background: #f3f2ee; }
/* 载入提示:vue.css 的 div#app { margin: 40vh auto } 会穿透到 body,载入完成时整页从屏幕中间跳到顶部 */
div#app { display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; color: #718078; font-size: 14px; letter-spacing: .04em; }
/* 载入完成前 body 不能滚动,出现滚动条时页面会左移;预留滚动条的位置 */
html { scrollbar-gutter: stable; }
body { color: #34495e; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; }
.app-name { font-weight: 650 !important; letter-spacing: .02em; }
.sidebar { padding-top: 24px; border-right: 1px solid #e2e3dc; }
@@ -26,25 +30,25 @@ body { color: #34495e; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI
.site-footer a { color: inherit; font-weight: 400; }
.topic-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 10px; margin: 18px 0; }
.topic-grid a { display: flex; align-items: center; justify-content: center; gap: 7px; padding: 13px 8px; border: 1px solid #dfe3da; border-radius: 7px; background: #fafbf8; text-decoration: none; font-size: 14px; white-space: nowrap; }
.topic-grid a:hover, .topic-grid a:focus-visible { background: #e8f1e9; border-color: #369b72; }
.topic-grid a:hover, .topic-grid a:focus-visible { background: #e8f1e9; border-color: #2a7a5a; }
.site-search { padding: 8px 15px; }
.site-search input { box-sizing: border-box; width: 100%; min-height: 42px; padding: 10px; border: 1px solid #dfe3da; color: #34495e; font-size: 15px; }
.site-search input:focus { outline: 2px solid #369b72; outline-offset: 1px; }
.site-search input:focus { outline: 2px solid #2a7a5a; outline-offset: 1px; }
.search-status { color: #718078; font-size: 12px; margin: 7px 0; }
.search-status:empty { display: none; }
.site-search .matching-post { display: block; padding: 12px 0; border-bottom: 1px solid #dde3d8; text-decoration: none; }
.matching-post strong, .matching-post small { display: block; }
.matching-post strong { color: #369b72; font-size: 14px; }
.matching-post strong { color: #2a7a5a; font-size: 14px; }
.matching-post small { color: #718078; margin: 5px 0; font-size: 12px; }
.matching-post p { color: #34495e; margin: 4px 0; font-size: 13px; line-height: 1.7; overflow-wrap: anywhere; }
.article-toc { border: 1px solid #dfe3da; border-radius: 7px; padding: 12px 18px; margin: 20px 0 28px; background: #fafbf8; }
.article-toc summary { color: #369b72; font-weight: 600; cursor: pointer; min-height: 24px; }
.article-toc summary { color: #2a7a5a; font-weight: 600; cursor: pointer; min-height: 24px; }
.article-toc > ul { margin-bottom: 0; }
.learn-banner, .learn-callout { display: flex; align-items: center; gap: 16px; margin: 18px 0; padding: 16px 18px; border: 1px solid #e3e6e4; border-left: 4px solid var(--theme-color); border-radius: 10px; background: #fafbfa; color: inherit; text-decoration: none !important; }
.learn-banner:hover, .learn-banner:focus-visible, .learn-callout:hover, .learn-callout:focus-visible { border-color: var(--theme-color); background: #eef7f2; }
.learn-banner-text { flex: 1; min-width: 0; }
.learn-banner b, .learn-callout b { display: block; color: var(--theme-color); font-size: 16px; }
.learn-banner-text span, .learn-callout span { display: block; margin-top: 2px; font-size: 14px; line-height: 1.6; opacity: .85; }
.learn-banner-text span, .learn-callout span { display: block; margin-top: 2px; font-size: 14px; line-height: 1.6; color: #34495e; }
.learn-banner-go { flex: none; padding: 7px 18px; border-radius: 8px; background: var(--theme-color); color: #fff; font-weight: 600; font-size: 15px; }
.learn-callout { display: block; }
.learn-practice { margin-top: 36px; }
@@ -90,5 +94,23 @@ body { color: #34495e; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI
/* 步骤动图的播放器 */
.anim-bar { display: flex; justify-content: center; align-items: center; gap: 8px; margin: 6px 0 4px; }
.anim-bar button { min-height: 30px; padding: 2px 12px; border: 1px solid #dfe3da; border-radius: 6px; background: #fafbf8; color: #34495e; font: inherit; font-size: 13px; cursor: pointer; }
.anim-bar button:hover, .anim-bar button:focus-visible { border-color: #369b72; color: #369b72; }
.anim-bar button:hover, .anim-bar button:focus-visible { border-color: #2a7a5a; color: #2a7a5a; }
.anim-count { min-width: 44px; color: #718078; font-size: 12px; font-variant-numeric: tabular-nums; }
/* 代码高亮:在 #f8f8f8 底上对比度不低于 4.5:1 */
.markdown-section code { color: #9a3f2a; }
.token.comment, .token.prolog, .token.doctype, .token.cdata { color: #646e69; }
.token.keyword, .token.function { color: #a3501b; }
.token.string, .token.attr-value { color: #2a7a52; }
.token.number, .token.boolean { color: #9b5a14; }
.token.namespace { opacity: 1; color: #58635d; }
.token.tag, .token.attr-name, .token.variable, .token.placeholder { color: #2d6b9a; }
.token.important { color: #a8401c; }
.token.entity, .token.control, .token.directive, .token.unit { color: #17788a; }
.token.selector { color: #5a52a3; }
.token.property { color: #7f6212; }
.token.atrule, .token.regex, .token.statement, .token.url { color: #17788a; }
.learn-q-label { background: #e7f1eb; }
/* 首页贡献者头像:链接按图片大小计算点击区域 */
.markdown-section > a:has(> img) { display: inline-block; margin: 2px; }
.markdown-section > a > img[src*="avatars"] { aspect-ratio: 1; }
+18 -2
View File
@@ -5,14 +5,29 @@
function encodedRoute(path) { return path.split('/').map(encodeURIComponent).join('/'); }
function configureImages(root, homepage) {
if (!root) return;
var first = root.querySelector('img');
root.querySelectorAll('img').forEach(function (img) {
if (homepage && img.classList.contains('site-logo')) {
img.loading = 'eager';
img.setAttribute('fetchpriority', 'high');
} else if (!img.hasAttribute('loading')) {
img.loading = 'lazy';
// 文章的第一张图常在首屏内,延迟加载会推迟最大内容绘制,直接加载
img.loading = !homepage && img === first ? 'eager' : 'lazy';
}
img.decoding = 'async';
// 笔记中的配图大多没写说明,读屏软件只能读出文件名;用所在小节的标题作为说明
if (!img.hasAttribute('alt')) {
var heading = null;
for (var node = img; node && node !== root && !heading; node = node.previousElementSibling || node.parentElement) {
if (/^H[1-6]$/.test(node.tagName) && node !== img) heading = node;
}
var title = heading ? heading.textContent.replace(/[#¶]/g, '').trim() : '';
img.alt = title ? title + (/[!-~]$/.test(title) ? ' ' : '') + '的配图' : '配图';
}
});
// 复制代码插件写的 aria-hidden="hidden" 不是有效值;插件的按钮在本插件之后才加上
setTimeout(function () {
root.querySelectorAll('.docsify-copy-code-button [aria-hidden="hidden"]').forEach(function (node) { node.setAttribute('aria-hidden', 'true'); });
});
}
function preserveLegacyLinks() {
@@ -258,7 +273,7 @@
if (!section || !sections || section.querySelector('.learn-q')) return;
var record = learnRecord(), today = localDay();
Object.keys(sections).forEach(function (anchor) {
var heading = document.getElementById(anchor), list = sections[anchor];
var heading = (section.ownerDocument || document).getElementById(anchor), list = sections[anchor];
if (!heading || !section.contains(heading) || !/^H[2-6]$/.test(heading.tagName)) return;
var tried = record ? list.filter(function (q) { return record.cards[q[0]]; }) : [];
var due = tried.filter(function (q) { var c = record.cards[q[0]]; return c.due && c.due <= today; }).length;
@@ -318,6 +333,7 @@
list.replaceWith(details);
details.appendChild(list);
}
sectionQuestions(vm, document.body);
return document.body.innerHTML + '<footer class="site-footer"><a href="https://github.com/CyC2018/CS-Notes" target="_blank" rel="noopener">CyC2018 · CS-Notes</a><span> · </span><a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">粤ICP备19071404号-1</a></footer>';
});
hook.doneEach(function () {