Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Animate nav item and panel activation #323

Merged
merged 29 commits into from
Mar 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7ebac3e
Animate nav item activation
PaperStrike Jul 5, 2021
b796e87
Animate nav panel changes by CSS
PaperStrike Jul 21, 2021
7ead651
More reliable toc panel height
PaperStrike Jul 22, 2021
dbf6d1b
Merge remote-tracking branch 'upstream/master' into nav-transition
PaperStrike Feb 6, 2023
603bf11
Fix some errors in the previous merge
PaperStrike Feb 6, 2023
f28b6c8
More reliable panel heights
PaperStrike Feb 6, 2023
1dede13
Keep a placeholder for pages with no TOC
PaperStrike Feb 6, 2023
9f14c5f
Fix initial load animations
PaperStrike Feb 6, 2023
64fe64b
Fix overview padding on pages without TOC
PaperStrike Feb 6, 2023
2aa06f1
Some simple cleaning
PaperStrike Feb 6, 2023
0b9de35
Prevent placeholder TOC changes in pjax switch
PaperStrike Feb 7, 2023
200977a
Remove overflowed transform transition
PaperStrike Feb 7, 2023
9183008
Fix the comment on transform condition
PaperStrike Feb 7, 2023
3c8a0d8
Fix the comment on the TOC transform delay
PaperStrike Feb 7, 2023
4fa30e4
Remove empty check in activateSidebarPanel
PaperStrike Feb 7, 2023
024bb9a
Differentiate .placeholder-toc in more cases
PaperStrike Feb 8, 2023
ef82237
Simplify nav child height calculation
PaperStrike Feb 8, 2023
ba47edd
Align nav transition/animation durations
PaperStrike Feb 11, 2023
f0aeb4f
Transition nav color changes
PaperStrike Feb 9, 2023
e8c1cc9
Calculate nav height in pure CSS
PaperStrike Feb 11, 2023
f2d19ba
Fix inactive panel height on initial load
PaperStrike Feb 11, 2023
28235d9
Align animation/transition timing function
PaperStrike Feb 11, 2023
6cd4be8
Align missed transition timing function
PaperStrike Feb 17, 2023
f406391
Remove a duplicated prop
PaperStrike Feb 17, 2023
5022ce9
Fix hidden panel being focusable
PaperStrike Feb 17, 2023
bf7bfb5
Fix hidden nav item being focusable
PaperStrike Feb 17, 2023
2e7977f
Fix fading/hidden nav being accessiable
PaperStrike Feb 17, 2023
81d0ef8
Fix panel CSS prop order
PaperStrike Feb 17, 2023
060bdda
Remove unneeded visibility transition delays
PaperStrike Feb 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 84 additions & 13 deletions source/css/_common/outline/sidebar/sidebar-nav.styl
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
// Sidebar Navigation
.sidebar-nav {
display: none;
font-size: $font-size-small;
height: 0;
margin: 0;
padding-bottom: 20px;
overflow: hidden;
padding-left: 0;
pointer-events: none;
transition: $transition-ease;
transition-property: height, visibility;
visibility: hidden;

.sidebar-nav-active & {
display: block;
height: "calc(%sem + 1px)" % $line-height-base;
pointer-events: unset;
visibility: unset;
}

li {
border-bottom: 1px solid transparent;
color: $sidebar-nav-color;
cursor: pointer;
display: inline-block;
font-size: $font-size-small;
transition: $transition-ease;
transition-property: border-bottom-color, color;

&.sidebar-nav-overview {
margin-left: 10px;
Expand All @@ -29,30 +37,93 @@
.sidebar-toc-active .sidebar-nav-toc, .sidebar-overview-active .sidebar-nav-overview {
border-bottom-color: $sidebar-highlight;
color: $sidebar-highlight;
transition-delay: $transition-duration;

&:hover {
color: $sidebar-highlight;
}
}

// Need for Sidebar/TOC inner scrolling if content taller then viewport.
// For TOC/Overview scrolling
.sidebar-panel-container {
align-items: start;
display: grid;
flex: 1;
overflow-x: hidden;
overflow-y: auto;
padding-top: 0;
transition: padding-top $transition-ease;

.sidebar-nav-active & {
padding-top: 20px;
}
}

.sidebar-panel {
display: none;
animation: deactivate-sidebar-panel $transition-duration ease-in-out;
grid-area: 1 / 1;
height: 0;
opacity: 0;
overflow: hidden;
pointer-events: none;
transform: translateY(0);
transition: $transition-ease;
transition-delay: 0s;
transition-property: opacity, transform, visibility;
visibility: hidden;

// Apply transform to both panels when sidebar nav is active,
// to the TOC panel when switching between Overview and TOC regardless of
// whether the sidebar nav is active
.sidebar-nav-active &,
.sidebar-overview-active &.post-toc-wrap {
transform: translateY(-20px);
}

// Delay TOC transform transition when switching from TOC to Overview and
// deactivating the sidebar nav at the same time, to prevent the TOC panel
// from moving too fast
// https://github.com/next-theme/hexo-theme-next/pull/323#issuecomment-1420780965
.sidebar-overview-active:not(.sidebar-nav-active) &.post-toc-wrap {
transition-delay: 0s, $transition-duration, 0s;
}

.sidebar-overview-active &.site-overview-wrap,
.sidebar-toc-active &.post-toc-wrap {
animation-name: activate-sidebar-panel;
height: auto;
opacity: 1;
pointer-events: unset;
transform: translateY(0);
// The visibility delay is intentionally set to 0s to accommodate
// the visibility change on initial page load.
transition-delay: $transition-duration, $transition-duration, 0s;
visibility: unset;
}

&.site-overview-wrap {
// Flexbox layout makes it possible to reorder the child
// elements of .site-overview-wrap through the `order` CSS property
flex-column();
gap: 10px;
justify-content: flex-start; // TODO: Optimize the duplicate with flex-column()
}
}

.sidebar-overview-active .site-overview-wrap {
// Flexbox layout makes it possible to reorder the child
// elements of .site-overview-wrap through the `order` CSS property
flex-column();
gap: 10px;
@keyframes deactivate-sidebar-panel {
from {
height: var(--inactive-panel-height, 0);
}
to {
height: var(--active-panel-height, 0);
}
}

.sidebar-toc-active .post-toc-wrap {
display: block;
@keyframes activate-sidebar-panel {
from {
height: var(--inactive-panel-height, auto);
}
to {
height: var(--active-panel-height, auto);
}
}
32 changes: 19 additions & 13 deletions source/css/_common/outline/sidebar/sidebar-toc.styl
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@ if (hexo-config('toc.enable')) {
ol {
list-style: none;
margin: 0;
padding: 0 2px 5px 10px;
padding: 0 2px 0 10px;
text-align: left;

> :last-child {
margin-bottom: 5px;
}

> ol {
padding-left: 0;
}
Expand All @@ -28,19 +32,21 @@ if (hexo-config('toc.enable')) {
}

.nav {
.nav-child {
display: hexo-config('toc.expand_all') ? block : none;
}

.active > .nav-child {
display: block;
}

.active-current > .nav-child {
display: block;
if (not hexo-config('toc.expand_all')) {
.nav-child {
--height: 0;
height: 0;
opacity: 0;
overflow: hidden;
transition-property: height, opacity, visibility;
transition: $transition-ease;
visibility: hidden;
}

> .nav-item {
display: block;
.active > .nav-child {
height: var(--height, auto);
opacity: 1;
visibility: unset;
}
}

Expand Down
7 changes: 4 additions & 3 deletions source/css/_variables/base.styl
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ $orange = #fc6423;

// Transition
// --------------------------------------------------
$transition-ease = .2s ease-in-out;
$transition-ease-in = .2s ease-in;
$transition-ease-out = .2s ease-out;
$transition-duration = .2s;
$transition-ease = $transition-duration ease-in-out;
$transition-ease-in = $transition-duration ease-in;
$transition-ease-out = $transition-duration ease-out;


// Scaffolding
Expand Down
18 changes: 16 additions & 2 deletions source/js/pjax.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,25 @@ const pjax = new Pjax({
selectors: [
'head title',
'script[type="application/json"]',
'.main-inner',
// Precede .main-inner to prevent placeholder TOC changes asap
'.post-toc-wrap',
'.main-inner',
'.languages',
'.pjax'
],
switches: {
'.post-toc-wrap': function(oldWrap, newWrap) {
if (newWrap.querySelector('.post-toc')) {
Pjax.switches.outerHTML.call(this, oldWrap, newWrap);
} else {
const curTOC = oldWrap.querySelector('.post-toc');
if (curTOC) {
curTOC.classList.add('placeholder-toc');
}
this.onSwitch();
}
}
},
analytics: false,
cacheBust: false,
scrollTo : !CONFIG.bookmark.enable
Expand All @@ -28,7 +42,7 @@ document.addEventListener('pjax:success', () => {
.bootstrap();
}
if (CONFIG.sidebar.display !== 'remove') {
const hasTOC = document.querySelector('.post-toc');
const hasTOC = document.querySelector('.post-toc:not(.placeholder-toc)');
document.querySelector('.sidebar-inner').classList.toggle('sidebar-nav-active', hasTOC);
NexT.utils.activateSidebarPanel(hasTOC ? 0 : 1);
NexT.utils.updateSidebarPosition();
Expand Down
101 changes: 61 additions & 40 deletions source/js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,19 @@ NexT.utils = {
});
},

updateActiveNav: function() {
if (!Array.isArray(NexT.utils.sections)) return;
let index = NexT.utils.sections.findIndex(element => {
return element && element.getBoundingClientRect().top > 10;
});
if (index === -1) {
index = NexT.utils.sections.length - 1;
} else if (index > 0) {
index--;
}
this.activateNavByIndex(index);
},

registerScrollPercent: function() {
const backToTop = document.querySelector('.back-to-top');
const readingProgressBar = document.querySelector('.reading-progress-bar');
Expand All @@ -138,16 +151,7 @@ NexT.utils = {
readingProgressBar.style.setProperty('--progress', scrollPercent.toFixed(2) + '%');
}
}
if (!Array.isArray(NexT.utils.sections)) return;
let index = NexT.utils.sections.findIndex(element => {
return element && element.getBoundingClientRect().top > 10;
});
if (index === -1) {
index = NexT.utils.sections.length - 1;
} else if (index > 0) {
index--;
}
this.activateNavByIndex(index);
this.updateActiveNav();
}, { passive: true });

backToTop && backToTop.addEventListener('click', () => {
Expand Down Expand Up @@ -263,7 +267,7 @@ NexT.utils = {
},

registerSidebarTOC: function() {
this.sections = [...document.querySelectorAll('.post-toc li a.nav-link')].map(element => {
this.sections = [...document.querySelectorAll('.post-toc:not(.placeholder-toc) li a.nav-link')].map(element => {
const target = document.getElementById(decodeURI(element.getAttribute('href')).replace('#', ''));
// TOC item animation navigate.
element.addEventListener('click', event => {
Expand All @@ -281,6 +285,7 @@ NexT.utils = {
});
return target;
});
this.updateActiveNav();
},

registerPostReward: function() {
Expand All @@ -292,18 +297,35 @@ NexT.utils = {
},

activateNavByIndex: function(index) {
const target = document.querySelectorAll('.post-toc li a.nav-link')[index];
const nav = document.querySelector('.post-toc:not(.placeholder-toc) .nav');
if (!nav) return;

const navItemList = nav.querySelectorAll('.nav-item');
const target = navItemList[index];
if (!target || target.classList.contains('active-current')) return;

document.querySelectorAll('.post-toc .active').forEach(element => {
element.classList.remove('active', 'active-current');
const singleHeight = navItemList[navItemList.length - 1].offsetHeight;

nav.querySelectorAll('.active').forEach(navItem => {
navItem.classList.remove('active', 'active-current');
});
target.classList.add('active', 'active-current');
let parent = target.parentNode;
while (!parent.matches('.post-toc')) {
if (parent.matches('li')) parent.classList.add('active');
parent = parent.parentNode;

let activateEle = target.querySelector('.nav-child') || target.parentElement;
let navChildHeight = 0;

while (nav.contains(activateEle)) {
if (activateEle.classList.contains('nav-item')) {
activateEle.classList.add('active');
} else { // .nav-child or .nav
// scrollHeight isn't reliable for transitioning child items.
// The last nav-item in a list has a margin-bottom of 5px.
navChildHeight += (singleHeight * activateEle.childElementCount) + 5;
activateEle.style.setProperty('--height', `${navChildHeight}px`);
}
activateEle = activateEle.parentElement;
}

// Scrolling to center active TOC element if TOC content is taller then viewport.
const tocElement = document.querySelector(CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini' ? '.sidebar-panel-container' : '.sidebar');
if (!document.querySelector('.sidebar-toc-active')) return;
Expand All @@ -318,7 +340,7 @@ NexT.utils = {
updateSidebarPosition: function() {
if (window.innerWidth < 1200 || CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini') return;
// Expand sidebar on post detail page by default, when post has a toc.
const hasTOC = document.querySelector('.post-toc');
const hasTOC = document.querySelector('.post-toc:not(.placeholder-toc)');
let display = CONFIG.page.sidebar;
if (typeof display !== 'boolean') {
// There's no definition sidebar in the page front-matter.
Expand All @@ -330,31 +352,30 @@ NexT.utils = {
},

activateSidebarPanel: function(index) {
const duration = 200;
const sidebar = document.querySelector('.sidebar-inner');
const panel = document.querySelector('.sidebar-panel-container');
const activeClassName = ['sidebar-toc-active', 'sidebar-overview-active'];
const activeClassNames = ['sidebar-toc-active', 'sidebar-overview-active'];
if (sidebar.classList.contains(activeClassNames[index])) return;

if (sidebar.classList.contains(activeClassName[index])) return;
const panelContainer = sidebar.querySelector('.sidebar-panel-container');
const tocPanel = panelContainer.firstElementChild;
const overviewPanel = panelContainer.lastElementChild;

window.anime({
duration,
targets : panel,
easing : 'linear',
opacity : 0,
translateY: [0, -20],
complete : () => {
// Prevent adding TOC to Overview if Overview was selected when close & open sidebar.
sidebar.classList.replace(activeClassName[1 - index], activeClassName[index]);
window.anime({
duration,
targets : panel,
easing : 'linear',
opacity : [0, 1],
translateY: [-20, 0]
});
let postTOCHeight = tocPanel.scrollHeight;
// For TOC activation, try to use the animated TOC height
if (index === 0) {
const nav = tocPanel.querySelector('.nav');
if (nav) {
postTOCHeight = parseInt(nav.style.getPropertyValue('--height'), 10);
}
});
}
const panelHeights = [
postTOCHeight,
overviewPanel.scrollHeight
];
panelContainer.style.setProperty('--inactive-panel-height', `${panelHeights[1 - index]}px`);
panelContainer.style.setProperty('--active-panel-height', `${panelHeights[index]}px`);

sidebar.classList.replace(activeClassNames[1 - index], activeClassNames[index]);
},

getScript: function(src, options = {}, legacyCondition) {
Expand Down