add animation for navigation bar indicator (#257)
This commit is contained in:
parent
8f5a4aadca
commit
b7c90a4206
|
@ -29,6 +29,8 @@
|
|||
firstTime = false
|
||||
this.refs.container.focus()
|
||||
}
|
||||
let { page } = this.get()
|
||||
this.store.set({currentPage: page})
|
||||
},
|
||||
store: () => store
|
||||
}
|
||||
|
|
|
@ -1,37 +1,16 @@
|
|||
<nav class="main-nav">
|
||||
<ul class="main-nav-ul">
|
||||
{#each $navPages as navPage (navPage.name)}
|
||||
<li class="main-nav-li">
|
||||
<NavItem {page} name="home" href="/" svg="#pinafore-logo" label="Home" />
|
||||
</li>
|
||||
<li class="main-nav-li">
|
||||
<NavItem {page} name="notifications" href="/notifications" svg="#fa-bell" label="Notifications" />
|
||||
</li>
|
||||
{#if $pinnedPage === '/local'}
|
||||
<li class="main-nav-li">
|
||||
<NavItem {page} name="local" href="/local" svg="#fa-users" label="Local" />
|
||||
</li>
|
||||
{:elseif $pinnedPage === '/federated'}
|
||||
<li class="main-nav-li">
|
||||
<NavItem {page} name="federated" href="/federated" svg="#fa-globe" label="Federated" />
|
||||
</li>
|
||||
{:elseif $pinnedPage === '/favorites'}
|
||||
<li class="main-nav-li">
|
||||
<NavItem {page} name="favorites" href="/favorites" svg="#fa-star" label="Favorites" />
|
||||
</li>
|
||||
{:elseif $pinnedPage.startsWith('/lists/')}
|
||||
<li class="main-nav-li">
|
||||
<NavItem {page} name="lists" href={$pinnedPage} svg="#fa-bars" label={$pinnedListTitle} />
|
||||
</li>
|
||||
{/if}
|
||||
<li class="main-nav-li">
|
||||
<NavItem {page} name="community" href="/community" svg="#fa-comments" label="Community" />
|
||||
</li>
|
||||
<li class="main-nav-li">
|
||||
<NavItem {page} name="search" href="/search" svg="#fa-search" label="Search" />
|
||||
</li>
|
||||
<li class="main-nav-li">
|
||||
<NavItem {page} name="settings" href="/settings" svg="#fa-gear" label="Settings" />
|
||||
<NavItem
|
||||
{page}
|
||||
name={navPage.name}
|
||||
href={navPage.href}
|
||||
svg={navPage.svg}
|
||||
label={navPage.label}
|
||||
/>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
aria-current={selected}
|
||||
on:click="onClick(event)"
|
||||
{href} >
|
||||
<div class="nav-icon-and-label">
|
||||
{#if name === 'notifications'}
|
||||
<div class="nav-link-svg-wrapper">
|
||||
<svg class="nav-link-svg">
|
||||
|
@ -20,11 +21,23 @@
|
|||
</svg>
|
||||
{/if}
|
||||
<span class="nav-link-label">{label}</span>
|
||||
</div>
|
||||
<div class="nav-indicator"
|
||||
nav-indicator-key={name}
|
||||
ref:indicator
|
||||
></div>
|
||||
</a>
|
||||
<style>
|
||||
.main-nav-link {
|
||||
border-bottom: 1px solid var(--nav-a-border);
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.nav-icon-and-label {
|
||||
padding: 15px 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
@ -62,18 +75,38 @@
|
|||
}
|
||||
|
||||
.main-nav-link.selected {
|
||||
border-bottom: 1px solid var(--nav-a-selected-border);
|
||||
background: var(--nav-a-selected-bg);
|
||||
}
|
||||
|
||||
.main-nav-link.selected:hover {
|
||||
border-bottom: 1px solid var(--nav-a-selected-border-hover);
|
||||
background: var(--nav-a-selected-bg-hover);
|
||||
}
|
||||
|
||||
.nav-indicator {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: var(--nav-a-border);
|
||||
transform-origin: left;
|
||||
}
|
||||
|
||||
.nav-indicator.animate {
|
||||
transition: transform 333ms ease-in-out;
|
||||
}
|
||||
|
||||
.main-nav-link:hover .nav-indicator {
|
||||
background: var(--nav-a-border-hover);
|
||||
}
|
||||
|
||||
.main-nav-link.selected .nav-indicator {
|
||||
background: var(--nav-a-selected-border);
|
||||
}
|
||||
|
||||
.main-nav-link.selected:hover .nav-indicator {
|
||||
background: var(--nav-a-selected-border-hover);
|
||||
}
|
||||
|
||||
.main-nav-link:hover {
|
||||
background-color: var(--nav-a-bg-hover);
|
||||
border-bottom: 1px solid var(--nav-a-border-hover);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
@ -106,7 +139,7 @@
|
|||
width: 25px;
|
||||
height: 25px;
|
||||
}
|
||||
.main-nav-link {
|
||||
.nav-icon-and-label {
|
||||
padding: 20px 0;
|
||||
}
|
||||
.nav-link-notifications {
|
||||
|
@ -118,8 +151,44 @@
|
|||
<script>
|
||||
import { store } from '../_store/store'
|
||||
import { smoothScrollToTop } from '../_utils/smoothScrollToTop'
|
||||
import { on, emit } from '../_utils/eventBus'
|
||||
import { mark, stop } from '../_utils/marks'
|
||||
import { doubleRAF } from '../_utils/doubleRAF'
|
||||
|
||||
export default {
|
||||
oncreate () {
|
||||
let { name } = this.get()
|
||||
let indicator = this.refs.indicator
|
||||
on('animateNavPart1', this, ({fromPage, toPage}) => {
|
||||
if (fromPage !== name) {
|
||||
return
|
||||
}
|
||||
mark('animateNavPart1 gBCR')
|
||||
let fromRect = indicator.getBoundingClientRect()
|
||||
stop('animateNavPart1 gBCR')
|
||||
emit('animateNavPart2', {fromRect, fromPage, toPage})
|
||||
})
|
||||
on('animateNavPart2', this, ({fromPage, fromRect, toPage}) => {
|
||||
if (toPage !== name) {
|
||||
return
|
||||
}
|
||||
mark('animateNavPart2 gBCR')
|
||||
let toRect = indicator.getBoundingClientRect()
|
||||
stop('animateNavPart2 gBCR')
|
||||
let translateX = fromRect.left - toRect.left
|
||||
let scaleX = fromRect.width / toRect.width
|
||||
indicator.style.transform = `translateX(${translateX}px) scaleX(${scaleX})`
|
||||
let onTransitionEnd = () => {
|
||||
indicator.removeEventListener('transitionend', onTransitionEnd)
|
||||
indicator.classList.remove('animate')
|
||||
}
|
||||
indicator.addEventListener('transitionend', onTransitionEnd)
|
||||
doubleRAF(() => {
|
||||
indicator.classList.add('animate')
|
||||
indicator.style.transform = ''
|
||||
})
|
||||
})
|
||||
},
|
||||
store: () => store,
|
||||
computed: {
|
||||
selected: ({ page, name }) => page === name,
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { instanceComputations } from './instanceComputations'
|
||||
import { timelineComputations } from './timelineComputations'
|
||||
import { navComputations } from './navComputations'
|
||||
|
||||
export function computations (store) {
|
||||
instanceComputations(store)
|
||||
timelineComputations(store)
|
||||
navComputations(store)
|
||||
}
|
||||
|
|
|
@ -47,17 +47,4 @@ export function instanceComputations (store) {
|
|||
['currentInstanceData'],
|
||||
(currentInstanceData) => currentInstanceData && currentInstanceData.access_token
|
||||
)
|
||||
|
||||
store.compute(
|
||||
'pinnedListTitle',
|
||||
['lists', 'pinnedPage'],
|
||||
(lists, pinnedPage) => {
|
||||
if (!pinnedPage.startsWith('/lists')) {
|
||||
return
|
||||
}
|
||||
let listId = pinnedPage.split('/').slice(-1)[0]
|
||||
let list = lists.find(_ => _.id === listId)
|
||||
return list ? list.title : ''
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
85
routes/_store/computations/navComputations.js
Normal file
85
routes/_store/computations/navComputations.js
Normal file
|
@ -0,0 +1,85 @@
|
|||
export function navComputations (store) {
|
||||
store.compute(
|
||||
'pinnedListTitle',
|
||||
['lists', 'pinnedPage'],
|
||||
(lists, pinnedPage) => {
|
||||
if (!pinnedPage.startsWith('/lists')) {
|
||||
return
|
||||
}
|
||||
let listId = pinnedPage.split('/').slice(-1)[0]
|
||||
let list = lists.find(_ => _.id === listId)
|
||||
return list ? list.title : ''
|
||||
}
|
||||
)
|
||||
|
||||
store.compute(
|
||||
'navPages',
|
||||
['pinnedPage', 'pinnedListTitle'],
|
||||
(pinnedPage, pinnedListTitle) => {
|
||||
let pinnedPageObject
|
||||
if (pinnedPage === '/federated') {
|
||||
pinnedPageObject = {
|
||||
name: 'federated',
|
||||
href: '/federated',
|
||||
svg: '#fa-globe',
|
||||
label: 'Federated'
|
||||
}
|
||||
} else if (pinnedPage === '/favorites') {
|
||||
pinnedPageObject = {
|
||||
name: 'favorites',
|
||||
href: '/favorites',
|
||||
svg: '#fa-star',
|
||||
label: 'Favorites'
|
||||
}
|
||||
} else if (pinnedPage.startsWith('/lists/')) {
|
||||
pinnedPageObject = {
|
||||
name: 'lists',
|
||||
href: pinnedPage,
|
||||
svg: '#fa-bars',
|
||||
label: pinnedListTitle
|
||||
}
|
||||
} else { // local
|
||||
pinnedPageObject = {
|
||||
name: 'local',
|
||||
href: '/local',
|
||||
svg: '#fa-users',
|
||||
label: 'Local'
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
name: 'home',
|
||||
href: '/',
|
||||
svg: '#pinafore-logo',
|
||||
label: 'Home'
|
||||
},
|
||||
{
|
||||
name: 'notifications',
|
||||
href: '/notifications',
|
||||
svg: '#fa-bell',
|
||||
label: 'Notifications'
|
||||
},
|
||||
pinnedPageObject,
|
||||
{
|
||||
name: 'community',
|
||||
href: '/community',
|
||||
svg: '#fa-comments',
|
||||
label: 'Community'
|
||||
},
|
||||
{
|
||||
name: 'search',
|
||||
href: '/search',
|
||||
svg: '#fa-search',
|
||||
label: 'Search'
|
||||
},
|
||||
{
|
||||
name: 'settings',
|
||||
href: '/settings',
|
||||
svg: '#fa-gear',
|
||||
label: 'Settings'
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
}
|
21
routes/_store/observers/navObservers.js
Normal file
21
routes/_store/observers/navObservers.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { emit } from '../../_utils/eventBus'
|
||||
|
||||
export function navObservers (store) {
|
||||
function pageIsInNav (store, page) {
|
||||
let { navPages } = store.get()
|
||||
return !!navPages.find(_ => _.name === page)
|
||||
}
|
||||
|
||||
store.observe('currentPage', (currentPage, previousPage) => {
|
||||
if (currentPage && previousPage &&
|
||||
pageIsInNav(store, currentPage) &&
|
||||
pageIsInNav(store, previousPage)) {
|
||||
emit('animateNavPart1', {
|
||||
fromPage: previousPage,
|
||||
toPage: currentPage
|
||||
})
|
||||
}
|
||||
}, {
|
||||
init: false
|
||||
})
|
||||
}
|
|
@ -2,10 +2,12 @@ import { instanceObservers } from './instanceObservers'
|
|||
import { timelineObservers } from './timelineObservers'
|
||||
import { notificationObservers } from './notificationObservers'
|
||||
import { onlineObservers } from './onlineObservers'
|
||||
import { navObservers } from './navObservers'
|
||||
|
||||
export function observers (store) {
|
||||
instanceObservers(store)
|
||||
timelineObservers(store)
|
||||
notificationObservers(store)
|
||||
onlineObservers(store)
|
||||
navObservers(store)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue