feat: add smooth scrolling capabilities

This commit is contained in:
Adrien Marquès 2022-10-05 16:07:36 +02:00
parent c4a0587c8c
commit f8f3b0e282
Signed by: xdrm-brackets
GPG Key ID: D75243CA236D825E
4 changed files with 70 additions and 4 deletions

View File

@ -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;

View File

@ -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.

View File

@ -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 {

57
src/service/scroller.ts Normal file
View File

@ -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);
}