[Style] navbar animation when changing fixed stat

This commit is contained in:
Tony Yang 2021-02-06 00:05:02 +08:00
parent 17a204f281
commit 32608acdc3
Signed by: t510599
GPG Key ID: D88388851C28715D
2 changed files with 121 additions and 25 deletions

View File

@ -2,19 +2,32 @@
@import '../font'; @import '../font';
$mobileView: 1024px; $mobileView: 1024px;
$fixedTopPadding: 25px;
$fixedRightPadding: 50px;
#news-nav { #news-nav {
font-size: 13pt; font-size: 13pt;
line-height: 1em; line-height: 1em;
z-index: 2; z-index: 2;
// prevent slow animation update
position: sticky; position: sticky;
top: 25px; top: $fixedTopPadding;
&.fixed { &.fixed {
position: fixed; position: fixed;
top: 25px; top: $fixedTopPadding;
right: 50px; right: $fixedRightPadding;
.news-nav.animating {
// from section to fixed
animation-name: move-to-side;
}
}
&.returning {
// prevent navbar overlap header
position: sticky;
} }
a { a {
@ -34,8 +47,14 @@ $mobileView: 1024px;
margin-left: 25px; margin-left: 25px;
transform: translateX(0); animation-duration: 0.5s;
transition: transform 0.5s ease-in-out; animation-timing-function: ease-in-out;
animation-fill-mode: forwards;
&.animating {
// from fixed to section
animation-name: move-to-section;
}
&-item { &-item {
margin: 0 8px; margin: 0 8px;
@ -74,25 +93,36 @@ $mobileView: 1024px;
} }
.nav-control-button { .nav-control-button {
color: $mid-blue; display: flex;
position: fixed;
z-index: 5;
top: 25px;
right: 50px;
border-radius: 5px;
border: none;
font-size: 24px;
position: flex;
flex-direction: column; flex-direction: column;
position: fixed;
top: $fixedTopPadding;
right: $fixedRightPadding;
z-index: 5;
padding: 0 15px;
width: 55px; width: 55px;
border: none;
border-radius: 5px;
color: $mid-blue;
font-size: 24px;
background-color: white; background-color: white;
box-shadow: -2px 0 15px $mid-blue; box-shadow: -2px 0 15px $mid-blue;
padding: 0 15px;
cursor: pointer; cursor: pointer;
p { p {
text-align: end; text-align: end;
} }
} }
// narrower padding
@media screen and (max-width: 1365px) {
.news-nav.animating {
animation-name: slide-to-section;
}
&.fixed .news-nav.animating {
animation-name: slide-to-side;
}
}
@media screen and (max-width: $mobileView) { @media screen and (max-width: $mobileView) {
.nav-control-button { .nav-control-button {
@ -103,14 +133,57 @@ $mobileView: 1024px;
text-align: left; text-align: left;
padding: 0 10px; padding: 0 10px;
} }
.news-nav.animating,
&.fixed .news-nav.animating {
animation-name: none;
}
} }
} }
@media screen and (max-width: $mobileView) { @media screen and (max-width: $mobileView) {
#news-nav,
#news-nav.fixed { #news-nav.fixed {
position: fixed;
top: 25px;
right: 20px; right: 20px;
} }
}
@keyframes move-to-section {
0% {
transform: translateX(280px - $fixedRightPadding);
}
100% {
transform: translateX(0);
}
}
@keyframes move-to-side {
0% {
transform: translateX(-(280px - $fixedRightPadding));
}
100% {
transform: translateX(0);
}
}
// for narrower padding
@keyframes slide-to-section {
0% {
transform: translateX(120px - $fixedRightPadding);
}
100% {
transform: translateX(0);
}
}
@keyframes slide-to-side {
0% {
transform: translateX(-(120px - $fixedRightPadding));
}
100% {
transform: translateX(0);
}
} }

View File

@ -1,6 +1,11 @@
<template> <template>
<nav id="news-nav" :class="{ fixed: isNavbarFixed }"> <nav id="news-nav" :class="{ fixed: (isNavbarFixed || isMobileView), returning: isNavbarReturning }">
<div v-show="!isMobileView || navVisible" class="news-nav" v-scrollspy="{ selectors: navbarItems }"> <div
class="news-nav"
:class="{ animating: isNavbarAnimating }"
v-show="!isMobileView || navVisible"
v-scrollspy="{ selectors: navbarItems }"
>
<a class="news-nav-item" href="#schedule">重要時程</a> <a class="news-nav-item" href="#schedule">重要時程</a>
<a class="news-nav-item" href="#example">投稿主題範例</a> <a class="news-nav-item" href="#example">投稿主題範例</a>
<a class="news-nav-item" href="#code-of-conduct">Code of Conduct</a> <a class="news-nav-item" href="#code-of-conduct">Code of Conduct</a>
@ -30,7 +35,9 @@ import ScrollSpyDirective from './ScrollSpyDirective';
}) })
export default class Navbar extends Vue { export default class Navbar extends Vue {
private isMobileView: boolean = false; private isMobileView: boolean = false;
private isNavbarFixed = false; private isNavbarFixed: boolean = false;
private isNavbarAnimating: boolean = false;
private isNavbarReturning: boolean = false;
private navVisible: boolean = false; private navVisible: boolean = false;
private navbarItems: string[] = [ private navbarItems: string[] = [
'#schedule', '#schedule',
@ -60,16 +67,32 @@ export default class Navbar extends Vue {
// observe header: header appears => no fix; header disappears => fix. // observe header: header appears => no fix; header disappears => fix.
const fixObserver: IntersectionObserver = new IntersectionObserver((entries, observer) => { const fixObserver: IntersectionObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => { entries.forEach((entry) => {
if (!entry.isIntersecting) { // no animation for mobile
this.isNavbarFixed = true; if (!this.isMobileView) {
} else { // only show animation if fix status has changed
this.isNavbarFixed = false; if (!entry.isIntersecting !== this.isNavbarFixed) {
if (!entry.isIntersecting) {
this.isNavbarFixed = true;
} else {
this.isNavbarFixed = false;
this.isNavbarReturning = true;
}
this.isNavbarAnimating = true;
}
} }
}); });
}, { rootMargin: '25px 0px 0px 0px', threshold: 0 }); }, { rootMargin: '25px 0px 0px 0px', threshold: 0 });
this.$nextTick().then(() => { this.$nextTick().then(() => {
fixObserver.observe(document.querySelector('#news-header') as Element); fixObserver.observe(document.querySelector('#news-header') as Element);
document.querySelector('.news-nav')?.addEventListener('animationend', (ev) => {
if (!this.isNavbarFixed) {
this.isNavbarReturning = false;
}
this.isNavbarAnimating = false;
});
if (location.hash) { if (location.hash) {
const hash = location.hash; const hash = location.hash;
// scroll to anchor // scroll to anchor