Repository navigation
Expand file tree
/
Copy pathapp.js
More file actions
98 lines (94 loc) · 4.63 KB
/
Copy pathapp.js
File metadata and controls
98 lines (94 loc) · 4.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
(() => {
'use strict';
const menuButton = document.querySelector('.menu-toggle');
const navigation = document.querySelector('#nav-links');
const setMenu = (open) => {
navigation.classList.toggle('is-open', open);
menuButton.setAttribute('aria-expanded', String(open));
menuButton.textContent = open ? '关闭' : '菜单';
};
menuButton.addEventListener('click', () => setMenu(menuButton.getAttribute('aria-expanded') !== 'true'));
navigation.addEventListener('click', (event) => { if (event.target.closest('a')) setMenu(false); });
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape' && menuButton.getAttribute('aria-expanded') === 'true') {
setMenu(false);
menuButton.focus();
}
});
document.addEventListener('click', (event) => { if (!event.target.closest('#nav')) setMenu(false); });
const progress = document.querySelector('#prog');
let scrollQueued = false;
const updateProgress = () => {
const page = document.documentElement;
const scrollRange = page.scrollHeight - page.clientHeight;
progress.style.width = `${scrollRange > 0 ? Math.min(100, page.scrollTop / scrollRange * 100) : 0}%`;
scrollQueued = false;
};
window.addEventListener('scroll', () => {
if (!scrollQueued) { scrollQueued = true; requestAnimationFrame(updateProgress); }
}, { passive: true });
window.addEventListener('resize', updateProgress);
updateProgress();
const lightbox = document.querySelector('#image-viewer');
const lightboxImage = lightbox.querySelector('img');
const lightboxCaption = lightbox.querySelector('.lightbox-caption');
let lastImage = null;
const closeImage = () => lightbox.close();
const openImage = (image) => {
lastImage = image;
lightboxImage.src = image.currentSrc || image.src;
lightboxImage.alt = image.alt;
lightboxCaption.textContent = image.closest('figure')?.querySelector('figcaption')?.textContent || image.alt;
lightbox.showModal();
document.body.classList.add('lightbox-open');
};
lightbox.querySelector('.lightbox-close').addEventListener('click', closeImage);
lightbox.addEventListener('click', (event) => { if (event.target === lightbox) closeImage(); });
lightbox.addEventListener('close', () => {
document.body.classList.remove('lightbox-open');
lightboxImage.removeAttribute('src');
lastImage?.focus({ preventScroll: true });
});
document.querySelectorAll('section img').forEach((image) => {
if (!image.alt) image.alt = image.closest('figure')?.querySelector('figcaption')?.textContent || '项目实验图';
if (!image.closest('.hero')) { image.loading = 'lazy'; image.decoding = 'async'; }
const canZoom = image.closest('figure:not(.hero-visual), .project-details .card') && !image.closest('.employer,.hero-affiliation,.hero-social');
if (!canZoom) return;
image.classList.add('image-zoom');
image.tabIndex = 0;
image.setAttribute('role', 'button');
image.setAttribute('aria-haspopup', 'dialog');
image.setAttribute('aria-label', `放大查看:${image.alt}`);
image.addEventListener('click', () => openImage(image));
image.addEventListener('keydown', (event) => {
if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); openImage(image); }
});
});
document.querySelectorAll('table').forEach((table) => {
const scroller = document.createElement('div');
scroller.className = 'table-scroll';
scroller.setAttribute('role', 'region');
scroller.setAttribute('aria-label', '实验结果表格,可横向滚动');
scroller.tabIndex = 0;
table.parentNode.insertBefore(scroller, table);
scroller.append(table);
table.querySelectorAll('th').forEach((heading) => heading.setAttribute('scope', 'col'));
});
document.querySelectorAll('[data-print]').forEach((button) => button.addEventListener('click', () => window.print()));
const anchorLinks = [...navigation.querySelectorAll('a[href^="#"]')];
const sections = anchorLinks.map((link) => document.querySelector(link.getAttribute('href'))).filter(Boolean);
if ('IntersectionObserver' in window) {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
anchorLinks.forEach((link) => {
const active = link.getAttribute('href') === `#${entry.target.id}`;
link.classList.toggle('active', active);
if (active) link.setAttribute('aria-current', 'location'); else link.removeAttribute('aria-current');
});
}
});
}, { rootMargin: '-15% 0px -65% 0px', threshold: 0 });
sections.forEach((section) => observer.observe(section));
}
})();