feat: add smooth scrolling capabilities
This commit is contained in:
parent
c4a0587c8c
commit
f8f3b0e282
|
@ -19,7 +19,7 @@ import SkillPicker from './components/SkillPicker.vue';
|
||||||
Home,
|
Home,
|
||||||
Timeline,
|
Timeline,
|
||||||
SkillPicker,
|
SkillPicker,
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
export default class App extends Vue {
|
export default class App extends Vue {
|
||||||
private selected: tID|null = null;
|
private selected: tID|null = null;
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
<h2>Featured in <b>{{ details.projects.length }}</b> {{ details.projects.length > 1 ? 'projects' : 'project' }}</h2>
|
<h2>Featured in <b>{{ details.projects.length }}</b> {{ details.projects.length > 1 ? 'projects' : 'project' }}</h2>
|
||||||
<h3>
|
<h3>
|
||||||
<template v-for='(proj) of details.projects'>
|
<template v-for='(proj) of details.projects'>
|
||||||
<a :key='"pick-" + proj.name' :href='"#p-" + proj.name'>
|
<a :key='"pick-" + proj.name' href @click='$event.preventDefault(); scroll(proj.name.replaceAll(" ", "_"));'>
|
||||||
{{ proj.name }}
|
{{ proj.name }}
|
||||||
</a>
|
</a>
|
||||||
<span :key='proj.name'>, </span>
|
<span :key='proj.name'>, </span>
|
||||||
|
@ -51,6 +51,7 @@
|
||||||
import { Project } from '@/model/projects';
|
import { Project } from '@/model/projects';
|
||||||
import * as skills from '@/service/skills';
|
import * as skills from '@/service/skills';
|
||||||
import * as projects from '@/service/projects';
|
import * as projects from '@/service/projects';
|
||||||
|
import { go } from '@/service/scroller';
|
||||||
|
|
||||||
interface Details {
|
interface Details {
|
||||||
icon: string|null;
|
icon: string|null;
|
||||||
|
@ -87,6 +88,14 @@
|
||||||
this.filterByTag();
|
this.filterByTag();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private scroll(proj_name: string) {
|
||||||
|
const head = document.querySelector(`#search-header`) as HTMLElement;
|
||||||
|
if( head == null ){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
go(`project-${proj_name}`, -1.2*head.offsetHeight)
|
||||||
|
}
|
||||||
|
|
||||||
// selects or deselects a skill. If the skill is not in the current
|
// selects or deselects a skill. If the skill is not in the current
|
||||||
// folder, it navigates to the DEFAULT_TAG folder beforehand. Scrolls to
|
// folder, it navigates to the DEFAULT_TAG folder beforehand. Scrolls to
|
||||||
// the selected skill when selected.
|
// the selected skill when selected.
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
<template class='project' v-for='(proj) of projects'>
|
<template class='project' v-for='(proj) of projects'>
|
||||||
<!-- id is used for navigation -->
|
<!-- id is used for navigation -->
|
||||||
<div :key="'start-'+proj.name" class='start' :id='"p-" + proj.name'>
|
<div :key="'start-'+proj.name" class='start' :id='"project-" + proj.name.replaceAll(" ", "_")'>
|
||||||
{{ proj.started_at | short_date }}
|
{{ proj.started_at | short_date }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@
|
||||||
return pluralize(diff/month, 'month');
|
return pluralize(diff/month, 'month');
|
||||||
}
|
}
|
||||||
return pluralize(diff/year, 'year');
|
return pluralize(diff/year, 'year');
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
export default class Timeline extends Vue {
|
export default class Timeline extends Vue {
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
|
||||||
|
let scroll_interval: number;
|
||||||
|
|
||||||
|
// scroll time in milliseconds
|
||||||
|
const SCROLL_MS = 500;
|
||||||
|
const SCROLL_MS_STEP = 5;
|
||||||
|
|
||||||
|
// go() scrolls to the element with the specified id with an optional offset.
|
||||||
|
// It achieves a smooth scrolling compared to the direct default one.
|
||||||
|
export function go(id: string, offset: number|undefined): void {
|
||||||
|
// find item with id
|
||||||
|
const item = document.querySelector(`#${id}`) as HTMLElement;
|
||||||
|
if( item == null ){
|
||||||
|
console.warn(`item with id '${id}' not found`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get item absolute y coordinates
|
||||||
|
let targety = absY(item, 0);
|
||||||
|
if( offset != null ){
|
||||||
|
targety += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearInterval(scroll_interval);
|
||||||
|
const step = (targety - document.body.scrollTop) / (SCROLL_MS / SCROLL_MS_STEP);
|
||||||
|
scroll_interval = setInterval(
|
||||||
|
() => smooth_scroll(targety, step),
|
||||||
|
SCROLL_MS_STEP
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function absY(item: HTMLElement|null, children_y: number): number {
|
||||||
|
if( item == null || item == document.body ){
|
||||||
|
return children_y;
|
||||||
|
}
|
||||||
|
return absY(item.parentElement, children_y + item.offsetTop);
|
||||||
|
}
|
||||||
|
|
||||||
|
// incremental smooth scroll
|
||||||
|
function smooth_scroll(target: number, step: number) {
|
||||||
|
const cur = document.body.scrollTop;
|
||||||
|
const max = (document.body as any).scrollTopMax;
|
||||||
|
|
||||||
|
// moving up & reached top or target -> stop
|
||||||
|
const upLimit = ( step < 0 && (cur <= 0 || cur <= target) );
|
||||||
|
// moving down & reached bottom or target -> stop
|
||||||
|
const downLimit = ( step > 0 && (cur >= max || cur >= target) );
|
||||||
|
|
||||||
|
if( upLimit || downLimit ){
|
||||||
|
clearInterval(scroll_interval);
|
||||||
|
document.body.scroll(0, target);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.scroll(0, cur + step);
|
||||||
|
}
|
Loading…
Reference in New Issue