|
| 1 | +- Start Date: 2020-05-31 |
| 2 | +- Target Major Version: Vue Router v3 and v4 |
| 3 | +- Reference Issues: https://github.com/vuejs/vue-router/issues/3008, |
| 4 | +- Implementation PR: (leave this empty) |
| 5 | + |
| 6 | +# Summary |
| 7 | + |
| 8 | +Deprecate `selector`, `x`, `y` and `offset` and introduce instead `el`, `top`, `left` (and `behavior`) that align with native apis and are more flexible. |
| 9 | + |
| 10 | +# Basic example |
| 11 | + |
| 12 | +```js |
| 13 | +const router = new Router({ |
| 14 | + scrollBehavior(to, from, savedPosition) { |
| 15 | + |
| 16 | + // scroll to id `can~contain-special>characters` + 200px |
| 17 | + return { |
| 18 | + el: '#can~contain-special>characters' |
| 19 | + // top relative offset |
| 20 | + top: 200 |
| 21 | + // instead of `offset: { y: 200 }` |
| 22 | + } |
| 23 | + } |
| 24 | +}) |
| 25 | +``` |
| 26 | + |
| 27 | +Other possible values returned by `scrollBehavior`: |
| 28 | + |
| 29 | +```js |
| 30 | +// scroll smoothly (when supported by the browser) to 400px from top and 20px from left |
| 31 | +{ |
| 32 | + top: 400, |
| 33 | + left: 20, |
| 34 | + behavior: 'smooth' |
| 35 | +} |
| 36 | + |
| 37 | +// scroll smoothly to selector .container |
| 38 | +{ |
| 39 | + el: '.container' |
| 40 | + behavior: 'smooth' |
| 41 | +} |
| 42 | + |
| 43 | +// directly pass an Element to `el` |
| 44 | +{ |
| 45 | + // use the fragment(to.hash) on the url but scroll to a child of it with a class `container` |
| 46 | + el: document.getElementById(to.hash.slice(1)).querySelector('.container') |
| 47 | +} |
| 48 | +``` |
| 49 | + |
| 50 | +# Motivation |
| 51 | + |
| 52 | +## Deprecating `selector` |
| 53 | + |
| 54 | +The existing scroll behavior accepts a `selector` property that internally uses `document.querySelector`. In vue-router@3 we currently have a workaround for selectors that match `/^#\d/` (id CSS selector starting with a number). This is because such selector is invalid, so we detect that and use `document.getElementById` instead. However, this breaks when using CSS combinators like `#1one .container` (select an element with class `.container` inside of an element with id `1one`). The truth is there ary many [others characters that need to be escaped](https://mathiasbynens.be/notes/css-escapes) and that we cannot escape everything, creating cases where the scroll behavior is harder to use. On top of that, it can be confusing for the user when vue-router throws because `document.querySelector` failed. |
| 55 | + |
| 56 | +## Deprecating `x`, `y` and `offset` |
| 57 | + |
| 58 | +Currently there are two ways of specifying an offset and both use an `x`/`y` coordinate system that differs from native functions: native browser functions allow the use of a [`ScrollToOptions`](https://developer.mozilla.org/en-US/docs/Web/API/ScrollToOptions) in `scrollTo` methods. This object contains `top`, `left` and `behavior` properties. |
| 59 | + |
| 60 | +# Detailed design |
| 61 | + |
| 62 | +## Deprecating `x` and `y` |
| 63 | + |
| 64 | +This aligns with [`Element.scrollTo`](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTo) and makes it more natural passing extra options like [`behavior`](https://developer.mozilla.org/en-US/docs/Web/API/ScrollToOptions/behavior). |
| 65 | + |
| 66 | +```js |
| 67 | +{ x: 0, y: 200 } |
| 68 | +// becomes |
| 69 | +{ left: 0, top: 200 } |
| 70 | +// it can accept `behavior` |
| 71 | +{ left: 0, top: 200, behavior: 'smooth' } |
| 72 | +``` |
| 73 | + |
| 74 | +## Deprecating `offset` |
| 75 | + |
| 76 | +Instead of taking an `offset` option alongside `selector`, we could directly accept `x` and `y` alongside `selector` when specifying an offset: |
| 77 | + |
| 78 | +```js |
| 79 | +{ |
| 80 | + selector: '#getting-started', |
| 81 | + y: -120, |
| 82 | +} |
| 83 | +``` |
| 84 | + |
| 85 | +Which, following the proposed rename, would become |
| 86 | + |
| 87 | +```js |
| 88 | +{ |
| 89 | + el: '#getting-started', |
| 90 | + top: -120, |
| 91 | +} |
| 92 | +``` |
| 93 | + |
| 94 | +Omitting `el` will create an absolute offset as if we were providing `x` and/or `y` in vue-router@3. |
| 95 | + |
| 96 | +This also aligns with `new Vue`'s `el` option |
| 97 | + |
| 98 | +## More intuitive selectors thanks to `el` |
| 99 | + |
| 100 | +The goal of a new `el` property, would be to provide simple, _"it just works"_, selectors for most common use cases while still allowing advanced cases: |
| 101 | + |
| 102 | +- `to.hash` refers to an _id_ on the page and is directly provided to `el`. e.g. `el: to.hash` when `to.hash` equals `#about`, `#getting-started`, or `#symbols~work` |
| 103 | +- `el` is provided a more generic selector, not necessarily coming from `to.hash`. e.g. `.container > main`, **anything not starting with `#`** |
| 104 | +- `el` is provided an `Element` to allow any advanced use case e.g. when `to.hash` starts with `#` but contains a CSS selector rather than an id: `document.querySelector(to.hash)` |
| 105 | + |
| 106 | +Thanks to allowing a raw Element, this covers all possible cases and makes it easier to provide the user with feedback when things do not work. |
| 107 | + |
| 108 | +## Developer experience through warnings |
| 109 | + |
| 110 | +Another important part of this change is warning in development when vue-router fails to find an element or when `document.querySelector` throws an Error. We can divide this in two cases: |
| 111 | + |
| 112 | +### `el` starts with `#` |
| 113 | + |
| 114 | +When `el` starts with `#`, we internally use `document.getElementById(el.slice(1))`. If it doesn't find an element, **in development** mode we try using `document.querySelector`, if we find something, we tell the user to use `el: document.querySelector(${providedEl})` and explain why. If we find nothing, show the usual warning of no element was found |
| 115 | + |
| 116 | +### `el` doesn't start with `#` |
| 117 | + |
| 118 | +In development mode, _try catch_ to provide an error message pointing to this great article https://mathiasbynens.be/notes/css-escapes and to [`CSS.escape`](https://developer.mozilla.org/en-US/docs/Web/API/CSS/escape) if `document.querySelector` throws. If nothing is found, display the same warning of no element was found. |
| 119 | + |
| 120 | +# Drawbacks |
| 121 | + |
| 122 | +- Breaking change but can be introduced through a deprecation |
| 123 | + |
| 124 | +# Adoption strategy |
| 125 | + |
| 126 | +- Deprecate `selector`, `x`, `y` and `offset` with a warning in v3 |
| 127 | +- Remove in v4 |
| 128 | + |
| 129 | +# Notes |
| 130 | + |
| 131 | +- This RFC does not concern scrolling to a different element than window or scrolling multiple elements at once |
0 commit comments