improvement

This commit is contained in:
myw
2026-07-14 14:41:41 +08:00 Unverified
parent f4a6f79142
commit 37233ab28b
18 changed files with 1718 additions and 1660 deletions
+156 -146
View File
@@ -2,18 +2,25 @@ document.addEventListener('DOMContentLoaded', () => {
let headerContentWidth, $nav
let mobileSidebarOpen = false
// rightsideScrollPercent
let goUpElement = null
let scrollPercentElement = null
const adjustMenu = init => {
const getAllWidth = ele => Array.from(ele).reduce((width, i) => width + i.offsetWidth, 0)
let hideMenuIndex = false
if (init) {
const blogInfoWidth = getAllWidth(document.querySelector('#blog-info > a').children)
const menusWidth = getAllWidth(document.getElementById('menus').children)
const blogInfoWidth = Array.from(document.querySelector('#blog-info > a').children).reduce((w, i) => w + i.offsetWidth, 0)
const menusWidth = Array.from(document.getElementById('menus').children).reduce((w, i) => w + i.offsetWidth, 0)
headerContentWidth = blogInfoWidth + menusWidth
$nav = document.getElementById('nav')
}
const hideMenuIndex = window.innerWidth <= 768 || headerContentWidth > $nav.offsetWidth - 120
$nav.classList.toggle('hide-menu', hideMenuIndex)
hideMenuIndex = window.innerWidth <= 768 || headerContentWidth > $nav.offsetWidth - 120
requestAnimationFrame(() => {
$nav.classList.toggle('hide-menu', hideMenuIndex)
})
}
// 初始化header
@@ -54,7 +61,7 @@ document.addEventListener('DOMContentLoaded', () => {
* 代碼
* 只適用於Hexo默認的代碼渲染
*/
const addHighlightTool = () => {
const addHighlightTool = $article => {
const highLight = GLOBAL_CONFIG.highlight
if (!highLight) return
@@ -64,8 +71,8 @@ document.addEventListener('DOMContentLoaded', () => {
const isNotHighlightJs = plugin !== 'highlight.js'
const isPrismjs = plugin === 'prismjs'
const $figureHighlight = isNotHighlightJs
? Array.from(document.querySelectorAll('code[class*="language-"]')).map(code => code.parentElement)
: document.querySelectorAll('figure.highlight')
? Array.from($article.querySelectorAll('code[class*="language-"]')).map(code => code.parentElement)
: $article.querySelectorAll('figure.highlight')
if (!((isShowTool || highlightHeightLimit) && $figureHighlight.length)) return
@@ -167,33 +174,23 @@ document.addEventListener('DOMContentLoaded', () => {
// 獲取隱藏狀態下元素的真實高度
const getActualHeight = item => {
if (item.offsetHeight > 0) return item.offsetHeight
const hiddenElements = new Map()
const fix = () => {
let current = item
while (current !== document.body && current != null) {
if (window.getComputedStyle(current).display === 'none') {
hiddenElements.set(current, current.getAttribute('style') || '')
}
current = current.parentNode
}
const clone = item.cloneNode(true)
const style = 'visibility: hidden !important; display: block !important;'
hiddenElements.forEach((originalStyle, elem) => {
elem.setAttribute('style', originalStyle ? originalStyle + ';' + style : style)
})
}
clone.style.cssText = `
position: absolute !important;
visibility: hidden !important;
display: block !important;
left: 0 !important;
top: 0 !important;
pointer-events: none !important;
z-index: -1 !important;
margin: 0 !important;
`
const restore = () => {
hiddenElements.forEach((originalStyle, elem) => {
if (originalStyle === '') elem.removeAttribute('style')
else elem.setAttribute('style', originalStyle)
})
}
fix()
const height = item.offsetHeight
restore()
item.parentNode.insertBefore(clone, item)
const height = clone.offsetHeight
clone.remove()
return height
}
@@ -244,9 +241,9 @@ document.addEventListener('DOMContentLoaded', () => {
/**
* PhotoFigcaption
*/
const addPhotoFigcaption = () => {
const addPhotoFigcaption = $article => {
if (!GLOBAL_CONFIG.isPhotoFigcaption) return
document.querySelectorAll('#article-container img').forEach(item => {
$article.querySelectorAll('img').forEach(item => {
const altValue = item.title || item.alt
if (!altValue) return
const ele = document.createElement('div')
@@ -259,8 +256,8 @@ document.addEventListener('DOMContentLoaded', () => {
/**
* Lightbox
*/
const runLightbox = () => {
btf.loadLightbox(document.querySelectorAll('#article-container img:not(.no-lightbox)'))
const runLightbox = $article => {
btf.loadLightbox($article.querySelectorAll('img:not(.no-lightbox)'))
}
/**
@@ -424,11 +421,11 @@ document.addEventListener('DOMContentLoaded', () => {
*/
const rightsideScrollPercent = currentTop => {
const scrollPercent = btf.getScrollPercent(currentTop, document.body)
const goUpElement = document.getElementById('go-up')
if (!goUpElement || !scrollPercentElement) return
if (scrollPercent < 95) {
goUpElement.classList.add('show-percent')
goUpElement.querySelector('.scroll-percent').textContent = scrollPercent
scrollPercentElement.textContent = scrollPercent
} else {
goUpElement.classList.remove('show-percent')
}
@@ -465,7 +462,7 @@ document.addEventListener('DOMContentLoaded', () => {
}
let flag = ''
const scrollTask = btf.throttle(() => {
const scrollTask = btf.rafThrottle(() => {
const currentTop = window.scrollY || document.documentElement.scrollTop
const isDown = scrollDirection(currentTop)
if (currentTop > 56) {
@@ -497,18 +494,17 @@ document.addEventListener('DOMContentLoaded', () => {
isShowPercent && rightsideScrollPercent(currentTop)
checkDocumentHeight()
}, 300)
})
btf.addEventListenerPjax(window, 'scroll', scrollTask, { passive: true })
}
/**
* toc,anchor
* toc, anchor
*/
const scrollFnToDo = () => {
const scrollFnToDo = $article => {
const isToc = GLOBAL_CONFIG_SITE.isToc
const isAnchor = GLOBAL_CONFIG.isAnchor
const $article = document.getElementById('article-container')
if (!($article && (isToc || isAnchor))) return
@@ -521,7 +517,6 @@ document.addEventListener('DOMContentLoaded', () => {
$tocPercentage = $cardTocLayout.querySelector('.toc-percentage')
isExpand = $cardToc.classList.contains('is-expand')
// toc元素點擊
const tocItemClickFn = e => {
const target = e.target.closest('.toc-link')
if (!target) return
@@ -548,86 +543,90 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
// 處理 hexo-blog-encrypt 事件
$cardToc.style.display = 'block'
}
// find head position & add active class
const $articleList = $article.querySelectorAll('h1,h2,h3,h4,h5,h6')
let detectItem = ''
if (!$articleList.length) return
// Optimization: Cache header positions
let headerList = []
const updateHeaderPositions = () => {
headerList = Array.from($articleList).map(ele => ({
ele,
top: btf.getEleTop(ele),
id: ele.id
}))
}
let activeTocItem = null
let activeParentItems = []
updateHeaderPositions()
const throttledUpdate = btf.throttle(updateHeaderPositions, 200)
btf.addEventListenerPjax(window, 'resize', throttledUpdate)
const updateTocUI = currentId => {
const encodedAnchor = currentId ? '#' + encodeURI(decodeURI(currentId)) : ''
if (isAnchor) btf.updateAnchor(encodedAnchor)
if ('ResizeObserver' in window) {
const observer = new ResizeObserver(throttledUpdate)
observer.observe($article)
btf.addGlobalFn('pjaxSendOnce', () => { observer.disconnect() })
}
if (!isToc) return
const findHeadPosition = top => {
if (top === 0) return false
if (activeTocItem) activeTocItem.classList.remove('active')
activeParentItems.forEach(i => i.classList.remove('active'))
activeParentItems = []
let currentId = ''
let currentIndex = ''
for (let i = 0; i < headerList.length; i++) {
const item = headerList[i]
if (top > item.top - 80) {
currentId = item.id ? '#' + encodeURI(item.id) : ''
currentIndex = i
} else {
break
}
if (!currentId) {
activeTocItem = null
return
}
if (detectItem === currentIndex) return
const targetLink = Array.from($tocLink).find(link => {
const href = link.getAttribute('href')
if (!href) return false
return decodeURI(href).replace('#', '') === decodeURI(currentId)
})
if (isAnchor) btf.updateAnchor(currentId)
if (!targetLink) return
detectItem = currentIndex
targetLink.classList.add('active')
activeTocItem = targetLink
setTimeout(() => autoScrollToc(targetLink), 0)
if (isToc) {
$cardToc.querySelectorAll('.active').forEach(i => i.classList.remove('active'))
if (currentId) {
const currentActive = $tocLink[currentIndex]
currentActive.classList.add('active')
setTimeout(() => autoScrollToc(currentActive), 0)
if (!isExpand) {
let parent = currentActive.parentNode
while (!parent.matches('.toc')) {
if (parent.matches('li')) parent.classList.add('active')
parent = parent.parentNode
}
if (!isExpand) {
let parent = targetLink.parentNode
while (!parent.matches('.toc')) {
if (parent.matches('li')) {
parent.classList.add('active')
activeParentItems.push(parent)
}
parent = parent.parentNode
}
}
}
// main of scroll
const tocScrollFn = btf.throttle(() => {
const observerOptions = {
root: null,
rootMargin: '-60px 0px -80% 0px',
threshold: 0
}
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
updateTocUI(entry.target.id)
}
})
}, observerOptions)
$articleList.forEach(ele => observer.observe(ele))
const scrollHandler = btf.rafThrottle(() => {
const currentTop = window.scrollY || document.documentElement.scrollTop
if (isToc && GLOBAL_CONFIG.percent.toc) {
$tocPercentage.textContent = btf.getScrollPercent(currentTop, $article)
}
findHeadPosition(currentTop)
}, 100)
btf.addEventListenerPjax(window, 'scroll', tocScrollFn, { passive: true })
if (currentTop === 0) {
updateTocUI('')
} else if (currentTop + window.innerHeight >= document.documentElement.scrollHeight - 10) {
const lastHeader = $articleList[$articleList.length - 1]
updateTocUI(lastHeader.id)
}
})
btf.addEventListenerPjax(window, 'scroll', scrollHandler, { passive: true })
btf.addGlobalFn('pjaxSendOnce', () => {
observer.disconnect()
})
}
const handleThemeChange = mode => {
@@ -804,8 +803,8 @@ document.addEventListener('DOMContentLoaded', () => {
/**
* table overflow
*/
const addTableWrap = () => {
const $table = document.querySelectorAll('#article-container table')
const addTableWrap = $article => {
const $table = $article.querySelectorAll('table')
if (!$table.length) return
$table.forEach(item => {
@@ -815,52 +814,54 @@ document.addEventListener('DOMContentLoaded', () => {
})
}
/**
* tag-hide
*/
const clickFnOfTagHide = () => {
const hideButtons = document.querySelectorAll('#article-container .hide-button')
const clickFnOfTagHide = $article => {
const hideButtons = $article.querySelectorAll('.hide-button')
if (!hideButtons.length) return
hideButtons.forEach(item => item.addEventListener('click', e => {
const currentTarget = e.currentTarget
currentTarget.classList.add('open')
addJustifiedGallery(currentTarget.nextElementSibling.querySelectorAll('.gallery-container'))
}, { once: true }))
const handleClickOfTagHide = e => {
const button = e.target.closest('.hide-button')
if (!button) return
button.classList.add('open')
addJustifiedGallery(button.nextElementSibling.querySelectorAll('.gallery-container'))
}
btf.addEventListenerPjax($article, 'click', handleClickOfTagHide)
}
const tabsFn = () => {
const navTabsElements = document.querySelectorAll('#article-container .tabs')
if (!navTabsElements.length) return
const tabsFn = $article => {
if (!$article.querySelector('.tabs')) return
const setActiveClass = (elements, activeIndex) => {
elements.forEach((el, index) => {
el.classList.toggle('active', index === activeIndex)
})
elements.forEach((el, index) => el.classList.toggle('active', index === activeIndex))
}
const handleNavClick = e => {
const target = e.target.closest('button')
if (!target || target.classList.contains('active')) return
const handleClick = e => {
const tabsRoot = e.target.closest('.tabs')
if (!tabsRoot) return
const navItems = [...e.currentTarget.children]
const tabContents = [...e.currentTarget.nextElementSibling.children]
const indexOfButton = navItems.indexOf(target)
setActiveClass(navItems, indexOfButton)
e.currentTarget.classList.remove('no-default')
setActiveClass(tabContents, indexOfButton)
addJustifiedGallery(tabContents[indexOfButton].querySelectorAll('.gallery-container'), true)
}
const navContainer = tabsRoot.firstElementChild
const toTopContainer = tabsRoot.lastElementChild
const handleToTopClick = tabElement => e => {
if (e.target.closest('button')) {
btf.scrollToDest(btf.getEleTop(tabElement), 300)
if (navContainer.contains(e.target)) {
const target = e.target.closest('button')
if (!target || target.classList.contains('active')) return
const navItems = [...navContainer.children]
const tabContents = [...navContainer.nextElementSibling.children]
const indexOfButton = navItems.indexOf(target)
setActiveClass(navItems, indexOfButton)
navContainer.classList.remove('no-default')
setActiveClass(tabContents, indexOfButton)
addJustifiedGallery(tabContents[indexOfButton].querySelectorAll('.gallery-container'), true)
return
}
if (toTopContainer.contains(e.target) && e.target.closest('button')) {
btf.scrollToDest(btf.getEleTop(tabsRoot), 300)
}
}
navTabsElements.forEach(tabElement => {
btf.addEventListenerPjax(tabElement.firstElementChild, 'click', handleNavClick)
btf.addEventListenerPjax(tabElement.lastElementChild, 'click', handleToTopClick(tabElement))
})
btf.addEventListenerPjax($article, 'click', handleClick)
}
const toggleCardCategory = () => {
@@ -926,10 +927,13 @@ document.addEventListener('DOMContentLoaded', () => {
}
const unRefreshFn = () => {
window.addEventListener('resize', () => {
const resizeHandler = btf.rafThrottle(() => {
adjustMenu(false)
mobileSidebarOpen && btf.isHidden(document.getElementById('toggle-menu')) && sidebarFn.close()
if (mobileSidebarOpen && btf.isHidden(document.getElementById('toggle-menu'))) {
sidebarFn.close()
}
})
window.addEventListener('resize', resizeHandler, { passive: true })
const menuMask = document.getElementById('menu-mask')
menuMask && menuMask.addEventListener('click', () => { sidebarFn.close() })
@@ -947,18 +951,24 @@ document.addEventListener('DOMContentLoaded', () => {
}
const forPostFn = () => {
addHighlightTool()
addPhotoFigcaption()
addJustifiedGallery(document.querySelectorAll('#article-container .gallery-container'))
runLightbox()
scrollFnToDo()
addTableWrap()
clickFnOfTagHide()
tabsFn()
const $article = document.getElementById('article-container')
if (!$article) return
addHighlightTool($article)
addPhotoFigcaption($article)
addJustifiedGallery($article.querySelectorAll('.gallery-container'))
runLightbox($article)
scrollFnToDo($article)
addTableWrap($article)
clickFnOfTagHide($article)
tabsFn($article)
}
const refreshFn = () => {
initAdjust()
goUpElement = document.getElementById('go-up')
scrollPercentElement = goUpElement?.querySelector('.scroll-percent')
justifiedIndexPostUI()
if (GLOBAL_CONFIG_SITE.pageType === 'post') {