Skip to content

Commit 54207df

Browse files
committed
release v0.9.9
1 parent ed2788f commit 54207df

File tree

7 files changed

+220
-219
lines changed

7 files changed

+220
-219
lines changed

.eslintignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
demo
22
tests
3-
content
4-
content-app
3+
content

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ node_modules
1212
dist
1313

1414
content
15-
content-app
1615
demo/dist
1716
*.tgz
1817
.eslintcache

README.md

Lines changed: 60 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,38 @@
55

66
**Examples:** Vite: [Demo App](https://vue-use-active-scroll.netlify.app) — Nuxt Content: [Nested TOC](https://stackblitz.com/edit/github-oh85gq?file=components%2FSidebar.vue)
77

8-
:bulb: Requires Vue 3 or above.
9-
108
<br />
119

1210
## Why?
1311

1412
The [Intersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) is a great API.
1513
But it may not be the one-size-fits-all solution to highlight menu/sidebar links.
1614

17-
When smooth-scrolling, you may want to immediately highlight targets when scroll is originated from click/navigation but not when it is originated from wheel/touch. You may also want to highlight any clicked link even if it will never intersect.
15+
_You may want to:_
16+
17+
- Highlight any clicked link even if it will never intersect
18+
- Get consistent results regardless of scroll speed
19+
- Immediately highlight links on click/hash navigation if smooth scrolling is enabled
20+
- Prevent unnatural highlighting with custom easings or smooth scrolling
1821

19-
**Vue Use Active Scroll** implements a custom scroll observer which automatically adapts to any type of scroll behaviors and interactions and always returns the "correct" active target.
22+
**Vue Use Active Scroll** implements a custom scroll observer which automatically adapts to any type of scroll behavior and interaction and always returns the "correct" active target.
2023

2124
### Features
2225

2326
- Precise and stable at any speed
2427
- CSS scroll-behavior or JS scroll agnostic
2528
- Adaptive behavior on mount, back/forward hash navigation, scroll, click, cancel.
26-
- Customizable boundary offsets for each direction
27-
- Customizable offsets for first/last targets
29+
- Customizable offsets for each scroll direction
30+
- Customizable offsets for first and last target
2831
- Customizable behavior on top/bottom reached
2932
- Supports custom scrolling containers
3033

3134
### What it doesn't do?
3235

33-
- Mutate elements and inject styles
34-
- Force specific scroll behavior / callbacks
3536
- Scroll to targets
37+
- Mutate elements and inject styles
38+
- Enforce specific scroll behavior / callbacks
39+
- Enforce the use of hash navigation
3640

3741
<br />
3842

@@ -59,7 +63,7 @@ Assuming your content looks like:
5963
<p>...</p>
6064
```
6165

62-
And your links look like:
66+
And your links will look like:
6367

6468
```html
6569
<a href="#introduction">Introduction</a>
@@ -70,20 +74,22 @@ And your links look like:
7074
In your menu/sidebar component, provide the IDs to observe to `useActive` (order is not
7175
important).
7276

77+
> :bulb: For a TOC, you may want to target (and scroll) the headings of your sections (instead of the whole section) to ensure results better-aligned with users' reading flow.
78+
7379
```vue
7480
<!-- Sidebar.vue -->
7581
7682
<script setup>
7783
import { useActive } from 'vue-use-active-scroll'
7884
79-
// Data to render links
85+
// Data to render links, in real life you may pass them as prop, use inject() etc...
8086
const links = ref([
8187
{ href: 'introduction', label: 'Introduction' },
8288
{ href: 'quick-start', label: 'Quick Start' },
8389
{ href: 'props', label: 'Props' }
8490
])
8591
86-
const targets = computed(() => links.map(({ href }) => href))
92+
const targets = computed(() => links.value.map(({ href }) => href))
8793
// console.log(targets.value) => ['introduction', 'quick-start', 'props']
8894
8995
const { isActive } = useActive(targets)
@@ -92,7 +98,41 @@ const { isActive } = useActive(targets)
9298

9399
You can provide either a reactive or a plain array of strings. If the array is reactive, the observer will reinitialize whenever it changes.
94100

95-
> :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.
101+
If an empty array is provided, the observer won't be initialized until the array is populated.
102+
103+
### What if my targets don't have IDs?
104+
105+
There might be cases where you lack control over the rendered HTML and no IDs nor TOC are provided. Assuming your content is wrapped by container with an ID (e.g. `#ArticleContent`):
106+
107+
```vue
108+
<!-- Sidebar.vue -->
109+
110+
<script setup>
111+
const links = ref([])
112+
113+
onMounted(() => {
114+
// 1. Get all the targets
115+
const targets = Array.from(
116+
document.getElementById('ArticleContent').querySelectorAll('h2')
117+
)
118+
119+
targets.forEach((target) => {
120+
// 2. Generate an ID from their text content and add it
121+
target.id = target.textContent.toLowerCase().replace(/\s+/g, '-')
122+
// 3. Populate the array to render links
123+
links.value.push({
124+
href: target.id,
125+
label: target.textContent
126+
})
127+
})
128+
})
129+
130+
// 4. Provide the IDs to observe
131+
const targets = computed(() => links.value.map(({ href }) => href))
132+
133+
const { isActive } = useActive(targets)
134+
</script>
135+
```
96136

97137
<details><summary><strong>Nuxt Content 2</strong></summary>
98138

@@ -180,6 +220,8 @@ const { isActive, setActive } = useActive(targets, {
180220

181221
### **1.** Call _setActive_ in your click handler by passing the anchor ID
182222

223+
> :bulb: This doesn't scroll to targets. It just informs the composable that scroll from click is about to happen and `useActive` will adjust its behavior accordingly.
224+
183225
```vue
184226
<!-- Sidebar.vue -->
185227
@@ -209,7 +251,7 @@ const { isActive, setActive } = useActive(targets)
209251

210252
You're free to choose between 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).
211253

212-
#### A. Using native CSS scroll-behavior
254+
#### A. Using native CSS scroll-behavior (recommended)
213255

214256
- If content is scrolled by the window, add the following CSS rule to your `html` element:
215257

@@ -226,7 +268,7 @@ html {
226268
scroll-behavior: auto; /* Keep it 'auto' */
227269
}
228270

229-
.container {
271+
.Container {
230272
scroll-behavior: smooth;
231273
}
232274
```
@@ -292,7 +334,7 @@ const { isActive, setActive } = useActive(targets)
292334
:key="link.href"
293335
:href="`#${link.href}`"
294336
:class="{
295-
active: isActive(link.href) /* 👈🏻 or link.href === activeId */
337+
ActiveLink: isActive(link.href) /* 👈🏻 or link.href === activeId */
296338
}"
297339
>
298340
{{ link.label }}
@@ -306,7 +348,7 @@ html {
306348
scroll-behavior: smooth; /* or 'auto' */
307349
}
308350
309-
.active {
351+
.ActiveLink {
310352
color: #f00;
311353
}
312354
</style>
@@ -355,7 +397,7 @@ html {
355397

356398
## Setting scroll-margin-top for fixed headers
357399

358-
You might noticed that if you have a fixed header and defined an `overlayHeight`, once you click to scroll to a target it may be underneath the header. You must add `scroll-margin-top` to your targets:
400+
You might noticed that if you have a fixed header and defined an `overlayHeight`, once clicked to scroll, the target may be underneath the header. You must add `scroll-margin-top` to your targets:
359401

360402
```js
361403
useActive(targets, { overlayHeight: 100 })
@@ -369,7 +411,7 @@ useActive(targets, { overlayHeight: 100 })
369411

370412
<br />
371413

372-
## Vue Router - Scroll to hash onMount / navigation
414+
## Vue Router - Scroll to hash on mount / navigation
373415

374416
> :warning: If using Nuxt 3, Vue Router is already configured to scroll to and from URL hash on page load or back/forward navigation. **So you don't need to do follow the steps below**. Otherwise rules must be defined manually.
375417
@@ -449,33 +491,6 @@ If you don't like that, choose to replace instead of pushing the hash:
449491

450492
<br />
451493

452-
## Custom initialization / re-initialization
453-
454-
If the targets array is empty, _useActive_ won't initialize the scroll observer.
455-
456-
Whenever `root` or `targets` are updated (and not empty), _useActive_ will re-initialize the observer.
457-
458-
```vue
459-
<script setup>
460-
// ...
461-
462-
const targets = ref([])
463-
const root = ref(null)
464-
465-
const { isActive, setActive } = useActive(targets) // Nothing is initialized
466-
467-
watch(someReactiveValue, async (newValue) => {
468-
await someAsyncFunction()
469-
470-
// Whenever ready, update targets or root and init
471-
targets.value = ['id-1', 'id-2', 'id-3']
472-
root.value = document.getElementById('MyContainer')
473-
})
474-
</script>
475-
```
476-
477-
<br />
478-
479494
## License
480495

481496
MIT

package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vue-use-active-scroll",
3-
"version": "0.9.8",
3+
"version": "0.9.9",
44
"private": false,
55
"description": "Reactive and accurate TOC/sidebar links without compromises for Vue 3.",
66
"keywords": [
@@ -45,20 +45,20 @@
4545
},
4646
"devDependencies": {
4747
"@rollup/plugin-terser": "^0.4.0",
48-
"@types/node": "^18.11.18",
49-
"@typescript-eslint/eslint-plugin": "^5.50.0",
50-
"@typescript-eslint/parser": "^5.50.0",
48+
"@types/node": "^18.13.0",
49+
"@typescript-eslint/eslint-plugin": "^5.51.0",
50+
"@typescript-eslint/parser": "^5.51.0",
5151
"@vitejs/plugin-vue": "^4.0.0",
5252
"animated-scroll-to": "^2.3.0",
53-
"cypress": "^12.5.0",
53+
"cypress": "^12.5.1",
5454
"eslint": "^8.33.0",
5555
"eslint-plugin-vue": "^9.9.0",
5656
"husky": "^8.0.3",
5757
"playwright-webkit": "^1.30.0",
58-
"prettier": "^2.8.3",
58+
"prettier": "^2.8.4",
5959
"rimraf": "^4.1.2",
6060
"typescript": "^4.9.5",
61-
"vite": "^4.1.0",
61+
"vite": "^4.1.1",
6262
"vite-plugin-dts": "^1.7.2",
6363
"vue": "^3.2.47",
6464
"vue-router": "^4.1.6",

0 commit comments

Comments
 (0)