Skip to content

Commit 3440285

Browse files
committed
edit readme
1 parent 6f3c9ec commit 3440285

File tree

2 files changed

+58
-116
lines changed

2 files changed

+58
-116
lines changed

README.md

Lines changed: 57 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,22 @@
99
## Why?
1010

1111
The [Intersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) is a great API.
12-
But it may not be the one-size-fits-all solution for highlighting menu/sidebar links. You might noticed that:
12+
But it may not be the one-size-fits-all solution to highlight menu/sidebar links.
1313

14-
- Scrolling speed affects accuracy
15-
- Clicking on some links highlights different targets (or does nothing).
16-
- Some targets never intersects once reached the bottom
17-
- On page load, active target doesn't match the one in the URL hash.
18-
- Is tricky to customize behavior according to different interactions
14+
You may noticed that clicking on some links highlights the wrong one (or does nothing) and that the active link may not reflect the one in the URL hash. But most important, you noticed that's tricky to obtain different behaviors according to different scroll interactions.
1915

20-
<br />
16+
For example, you want to immediately highlight targets when scroll is originated from click but not when scroll is originated from wheel/touch.
2117

22-
**Vue Use Active Scroll** implements a custom scroll observer that automatically deals with all these drabacks and simply returns the correct active target.
18+
**Vue Use Active Scroll** implements a custom scroll observer which automatically adapts to different interactions and always returns the "correct" active target.
2319

2420
### Features
2521

26-
- Precise and stable at any scroll speed
27-
- Customizable boundary offsets for each scroll direction
22+
- Precise and stable at any speed
23+
- CSS scroll-behavior and callback agnostic
24+
- Adaptive behavior on mount, scroll, click, cancel.
25+
- Customizable boundary offsets for each direction
2826
- Customizable behavior on top/bottom reached
2927
- Supports containers different than window
30-
- Adaptive behavior on mount, scroll, click, cancel.
31-
- CSS scroll-behavior and click callback agnostic
3228

3329
### What it doesn't do?
3430

@@ -49,108 +45,56 @@ pnpm add vue-use-active-scroll
4945

5046
## 1. Provide target IDs
5147

52-
In your menu/sidebar component, provide the IDs of the targets to observe to `useActive` (order is not important):
53-
54-
> :bulb: For a TOC, you most likely want to target (and scroll) the headings of your content (instead of the whole section) to ensure results coherent with users' reading flow.
55-
56-
```vue
57-
<script setup>
58-
import { useActive } from 'vue-reactive-toc'
59-
60-
const targets = ref(['introduction', 'quick-start', 'props', 'events'])
61-
// or 'reactive' or 'computed' or plain array of strings
48+
Assuming your content looks like:
6249

63-
const { isActive } = useActive(targets)
64-
</script>
50+
```html
51+
<h2 id="introduction">Introduction</h2>
52+
<p>...</p>
53+
<h2 id="quick-start">Quick Start</h2>
54+
<p>...</p>
55+
<h2 id="props">Props</h2>
56+
<p>...</p>
6557
```
6658

67-
<details><summary><strong>Using Inject</strong></summary>
59+
And your links look like:
6860

69-
<br />
70-
71-
```vue
72-
<!-- PageLayout.vue -->
73-
74-
<script setup>
75-
impoty { ref, provide } from 'vue'
76-
77-
const targets = ref(['introduction', 'quick-start', 'props', 'events'])
78-
// You most likely will compute them from your content
79-
80-
provide('SidebarData', {
81-
targets
82-
// Other stuff...
83-
})
84-
</script>
85-
86-
<template>
87-
<!-- <Content /> -->
88-
<Sidebar :targets="targets" />
89-
</template>
61+
```html
62+
<nav>
63+
<a href="#introduction">Introduction</a>
64+
<a href="#quick-start">Quick Start</a>
65+
<a href="#props">Props</a>
66+
</nav>
9067
```
9168

92-
```vue
93-
<!-- Sidebar.vue -->
94-
95-
<script setup>
96-
import { inject } from 'vue'
97-
import { useActive } from 'vue-reactive-toc'
98-
99-
const { targets /* ...other stuff */ } = inject('SidebarData')
100-
101-
const { isActive } = useActive(targets)
102-
</script>
103-
```
104-
105-
</details>
106-
107-
<details><summary><strong>Using Props</strong></summary>
108-
109-
<br />
110-
111-
```vue
112-
<!-- PageLayout.vue -->
113-
114-
<script setup>
115-
impoty { ref } from 'vue'
116-
117-
const targets = ref(['introduction', 'quick-start', 'props', 'events'])
118-
// You most likely will compute them from your content
119-
</script>
120-
121-
<template>
122-
<!-- <Content /> -->
123-
<TocSidebar :targets="targets" />
124-
</template>
125-
```
69+
In your menu/sidebar component, provide the IDs of the targets to observe to `useActive` (order is not
70+
important).
12671

12772
```vue
12873
<!-- Sidebar.vue -->
12974
13075
<script setup>
131-
impoty { toRef } from 'vue'
13276
import { useActive } from 'vue-reactive-toc'
13377
134-
const props = defineProps({
135-
targets: {
136-
type: Array,
137-
required: true
138-
}
139-
})
78+
const links = ref([
79+
{ href: 'introduction', label: 'Introduction' },
80+
{ href: 'quick-start', label: 'Quick Start' },
81+
{ href: 'props', label: 'Props' }
82+
]) // Data used to render your links
14083
141-
const targets = toRef(props, 'targets')
84+
const targets = computed(() => links.map(({ href }) => href))
85+
// console.log(targets.value) => ['introduction', 'quick-start', 'props']
14286
14387
const { isActive } = useActive(targets)
14488
</script>
14589
```
14690

147-
</details>
91+
> :bulb: For a TOC, you want to target (and scroll) the headings of your sections (instead of the whole section) to ensure results better-aligned with users' reading flow.
14892
14993
<details><summary><strong>Nuxt Content</strong></summary>
15094

15195
<br />
15296

153-
Nuxt Content automatically applies IDs to your headings. You can get the TOC links by accessing `data.body.toc.links` using [queryContent](https://content.nuxtjs.org/api/composables/query-pages/).
97+
Nuxt Content automatically applies IDs to your headings. You can get the TOC links by accessing `data.body.toc.links` via [queryContent](https://content.nuxtjs.org/api/composables/query-pages/).
15498

15599
```js
156100
const { data } = await useAsyncData('about', () => queryContent('/about').findOne())
@@ -199,14 +143,14 @@ const { isActive } = useActive(targets)
199143

200144
```js
201145
const { isActive, setActive } = useActive(targets, {
202-
// Options...
146+
// ...
203147
})
204148
```
205149

206150
| Property | Type | Default | Description |
207151
| -------------- | ------------------ | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
208152
| jumpToFirst | `boolean` | true | Whether to set the first target on mount as active even if not (yet) intersecting. |
209-
| jumpToLast | `boolean` | true | Whether to set the last target as active once reached the bottom. |
153+
| jumpToLast | `boolean` | true | Whether to set the last target as active once reached the bottom even if previous targets are entirely visible. |
210154
| boundaryOffset | `BoundaryOffset` | { toTop: 0, toBottom: 0 } | Boundary offset in px for each scroll direction. Tweak them to "anticipate" or "delay" target detection. |
211155
| rootId | `string` \| `null` | null | Id of the scrolling element. Set it only if your content **is not scrolled** by the window. |
212156
| replaceHash | `boolean` | false | Whether to replace URL hash on scroll. First target is ignored if `jumpToFirst` is true. |
@@ -228,8 +172,6 @@ const { isActive, setActive } = useActive(targets, {
228172

229173
### **1.** Call `setActive` in your click handler by passing the anchor ID
230174

231-
> :bulb: _setActive_ doesn't scroll to the target but ensures proper behavior between any interaction which may trigger or cancel a scroll event.
232-
233175
```vue
234176
<script setup>
235177
// ...
@@ -239,10 +181,10 @@ const { isActive, setActive } = useActive(targets)
239181
<template>
240182
<nav>
241183
<a
242-
@click="setActive(link.targetId) /* 👈🏻 */"
184+
@click="setActive(link.href) /* 👈🏻 */"
243185
v-for="(link, index) in links"
244-
:key="link.targetId"
245-
:to="`#${link.targetId}`"
186+
:key="link.href"
187+
:href="`#${link.href}`"
246188
>
247189
{{ link.label }}
248190
</a>
@@ -257,7 +199,7 @@ html {
257199
</style>
258200
```
259201

260-
You are totally free to create your own click handler and choose the scrolling strategy: CSS (smooth or auto), [scrollIntoView](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView) or even a scroll library like [animated-scroll-to](https://github.com/Stanko/animated-scroll-to) with custom easings will work. Just remember to include `setActive` in your handler.
202+
Feel free to create your own click handler and to choose the scrolling strategy: CSS (smooth or auto), [scrollIntoView](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView) or even a library like [animated-scroll-to](https://github.com/Stanko/animated-scroll-to) with custom easings will work. Just remember to include `setActive` in your handler.
261203

262204
<details><summary><strong>Custom Scroll Callback</strong></summary>
263205

@@ -272,10 +214,10 @@ import animateScrollTo from 'animated-scroll-to'
272214
273215
const { isActive, setActive } = useActive(targets)
274216
275-
function scrollTo(event, targetId) {
217+
function scrollTo(event, id) {
276218
// ...
277-
setActive(targetId) // 👈🏻 Include setActive
278-
animateScrollTo(document.getElementById(targetId), {
219+
setActive(id) // 👈🏻 Include setActive
220+
animateScrollTo(document.getElementById(id), {
279221
easing: easeOutBack,
280222
minDuration: 300,
281223
maxDuration: 600
@@ -287,9 +229,9 @@ function scrollTo(event, targetId) {
287229
<!-- ... -->
288230
<a
289231
v-for="(link, index) in links"
290-
@click="scrollTo($event, link.targetId)"
291-
:key="link.targetId"
292-
:to="`#${link.targetId}`"
232+
@click="scrollTo($event, link.href)"
233+
:key="link.href"
234+
:href="`#${link.href}`"
293235
>
294236
{{ link.label }}
295237
</a>
@@ -312,12 +254,12 @@ const { isActive, setActive } = useActive(targets)
312254
<template>
313255
<nav>
314256
<a
315-
@click="setActive(link.targetId)"
257+
@click="setActive(link.href)"
316258
v-for="(link, index) in links"
317-
:key="link.targetId"
318-
:to="`#${link.targetId}`"
259+
:key="link.href"
260+
:href="`#${link.href}`"
319261
:class="{
320-
active: isActive(link.targetId) /* 👈🏻 or link.targetId === activeId */
262+
active: isActive(link.href) /* 👈🏻 or link.href === activeId */
321263
}"
322264
>
323265
{{ link.label }}
@@ -343,12 +285,12 @@ html {
343285

344286
```vue
345287
<RouterLink
346-
@click.native="setActive(link.targetId)"
347-
:to="{ hash: `#${link.targetId}` }"
288+
@click.native="setActive(link.href)"
289+
:to="{ hash: `#${link.href}` }"
348290
:class="{
349-
active: isActive(link.targetId)
291+
active: isActive(link.href)
350292
}"
351-
:ariaCurrentValue="`${isActive(link.targetId)}`"
293+
:ariaCurrentValue="`${isActive(link.href)}`"
352294
activeClass=""
353295
exactActiveClass=""
354296
>
@@ -364,10 +306,10 @@ html {
364306

365307
```vue
366308
<NuxtLink
367-
@click="setActive(link.targetId)"
368-
:href="`#${link.targetId}`"
309+
@click="setActive(link.href)"
310+
:href="`#${link.href}`"
369311
:class="{
370-
active: isActive(link.targetId)
312+
active: isActive(link.href)
371313
}"
372314
>
373315
{{ link.label }}

src/useActive.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export function useActive(
9898
}
9999
});
100100

101-
_targets.sort((a, b) => a.offsetTop - b.offsetTop);
101+
_targets.sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
102102
targets.elements = _targets;
103103

104104
const rootTop = getTop();

0 commit comments

Comments
 (0)