{"id":13807716,"url":"https://github.com/DockYard/ember-router-scroll","last_synced_at":"2025-05-14T00:31:55.119Z","repository":{"id":35110421,"uuid":"63825881","full_name":"DockYard/ember-router-scroll","owner":"DockYard","description":"🗔 Scroll to top with preserved browser history scroll position. ","archived":false,"fork":false,"pushed_at":"2023-06-20T09:55:11.000Z","size":5454,"stargazers_count":204,"open_issues_count":27,"forks_count":57,"subscribers_count":32,"default_branch":"master","last_synced_at":"2025-05-08T14:03:19.625Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://dollarshaveclub.github.io/router-scroll-demo/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DockYard.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2016-07-21T01:14:59.000Z","updated_at":"2024-02-10T18:29:09.000Z","dependencies_parsed_at":"2024-01-26T05:18:08.857Z","dependency_job_id":null,"html_url":"https://github.com/DockYard/ember-router-scroll","commit_stats":{"total_commits":233,"total_committers":45,"mean_commits":5.177777777777778,"dds":0.8454935622317596,"last_synced_commit":"2f17728fd82d7d486888df9a09fdc4aae233f294"},"previous_names":["dollarshaveclub/ember-router-scroll"],"tags_count":80,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DockYard%2Fember-router-scroll","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DockYard%2Fember-router-scroll/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DockYard%2Fember-router-scroll/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DockYard%2Fember-router-scroll/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DockYard","download_url":"https://codeload.github.com/DockYard/ember-router-scroll/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254046371,"owners_count":22005577,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-04T01:01:29.360Z","updated_at":"2025-05-14T00:31:50.100Z","avatar_url":"https://github.com/DockYard.png","language":"JavaScript","funding_links":[],"categories":["Packages"],"sub_categories":["Routing addons"],"readme":"ember-router-scroll\n==============================================================================\n\n[![GitHub Actions Build Status](https://github.com/DockYard/ember-router-scroll/workflows/CI/badge.svg)](https://github.com/DockYard/ember-router-scroll/actions/workflows/ci.yml?query=branch%3Amaster)\n\n\u003e Scroll to page top on transition, like a non-SPA website. An alternative scroll behavior for Ember applications.\n\n## Why Use it?\n\nEmber expects an application to be rendered with nested views. The default behavior is for the scroll position to be\npreserved on every transition. However, not all Ember applications use nested views. For these applications, a user\nwould expect to see the top of the page on most transitions.\n\nIn addition to scrolling to the top of the page on most transitions, a user would expect the scroll position to be\npreserved when using the back or forward browser buttons.\n\n**ember-router-scroll** makes your single page application feel more like a regular website.\n\nCompatibility\n------------------------------------------------------------------------------\n\n* Ember.js v3.12 or above\n* Ember CLI v3.12 or above\n* Node.js v12 or above\n\n\nInstallation\n------------------------------------------------------------------------------\n\n```sh\nember install ember-router-scroll\n```\n\n\nUsage \u003e 4.x\n------------------------------------------------------------------------------\n\nUsers do not need to import and extend from `ember-router-scroll` anymore.  In order to upgrade, you should remove this import.\n\nThis is what your `router.js` should look like.\n\n```js\nimport EmberRouter from '@ember/routing/router';\n\nexport default class Router extends EmberRouter {\n  ...\n}\n```\n\nUsage \u003c 4.x\n------------------------------------------------------------------------------\n\n**1.** Import ember-router-scroll\n\nAdd RouterScroll as an extension to your Router object.  This class extends EmberRouter.\n\n```javascript\n// app/router.js\n\nimport EmberRouterScroll from 'ember-router-scroll';\n\nclass Router extends EmberRouterScroll {\n  ...\n}\n```\n\nIn version prior to v2.0, you can import the mixin and use it like so.  This is necessary if your application does not support JavaScript classes yet.\n\n```javascript\n// app/router.js\n\nimport RouterScroll from 'ember-router-scroll';\n\nconst Router = EmberRouter.extend(RouterScroll, {\n  ...\n});\n```\n\nRemaining optional steps for all versions 2.x - 4.x\n------------------------------------------------------------------------------\n\n**2.** Enable `historySupportMiddleware` in your app\n\nEdit `config/environment.js` and add `historySupportMiddleware: true,` to get live-reload working in nested routes.\n(See [Issue #21](https://github.com/DockYard/ember-router-scroll/issues/21))\n\n```javascript\nhistorySupportMiddleware: true,\n```\n\nThis location type inherits from Ember's `HistoryLocation`.\n```\n\n\n### Options\n\n#### Target Elements\n\nIf you need to scroll to the top of an area that generates a vertical scroll bar, you can specify the id of an element\nof the scrollable area. Default is `window` for using the scroll position of the whole viewport. You can pass an options\nobject in your application's `config/environment.js` file.\n\n```javascript\nENV['routerScroll'] = {\n  scrollElement: '#mainScrollElement'\n};\n```\n\nIf you want to scroll to a target element on the page, you can specify the id or class of the element on the page.  This\nis particularly useful if instead of scrolling to the top of the window, you want to scroll to the top of the main\ncontent area (that does not generate a vertical scrollbar).\n\n```javascript\nENV['routerScroll'] = {\n  targetElement: '#main-target-element' // or .main-target-element\n};\n```\n\n#### Scroll Timing\n\nYou may want the default \"out of the box\" behaviour.  We schedule scroll immediately after Ember's `render`.  This occurs on the tightest schedule between route transition start and end.\n\nHowever, you have other options. If you need an extra tick after `render`, set `scrollWhenAfterRender: true`.  You also may need to delay scroll functionality until the route is idle (approximately after the first paint completes) using `scrollWhenIdle: true` in your config.  `scrollWhenIdle` \u0026\u0026 `scrollWhenAfterRender` defaults to `false`.\n\nThis config property uses [`ember-app-scheduler`](https://github.com/ember-app-scheduler/ember-app-scheduler), so be sure to follow the instructions in the README.  We include the `setupRouter` and `reset`.  This all happens after `routeDidChange`.\n\n```javascript\nENV['routerScroll'] = {\n  scrollWhenIdle: true // ember-app-scheduler\n};\n```\n\nOr\n\n```js\nENV['routerScroll'] = {\n  scrollWhenAfterRender: true // scheduleOnce('afterRender', ...)\n};\n```\nI would suggest trying all of them out and seeing which works best for your app!\n\n\n## A working example\n\nSee [demo](https://dollarshaveclub.github.io/router-scroll-demo/) made by [Jon Chua](https://github.com/Chuabacca/).\n\n\n## A visual demo\n\n### Before\n\n![before-scroll](https://cloud.githubusercontent.com/assets/4430436/17122972/0a1fe454-5295-11e6-937f-f1f5beab9d6b.gif)\n\nNotice that the in the full purple page, the user is sent to the **middle** of the page.\n\n\n### After\n\n![after-scroll](https://cloud.githubusercontent.com/assets/4430436/17122970/07c1a3a0-5295-11e6-977f-37eb955d95b1.gif)\n\nNotice that the in the full purple page, the user is sent to the **top** of the page.\n\n\n## Issues with nested routes\n\n### Before:\n\n![before-preserve](https://cloud.githubusercontent.com/assets/4430436/17122971/0a1e34ce-5295-11e6-8d30-9f687dd69dbb.gif)\n\nNotice the unwanted scroll to top in this case.\n\n\n### After:\n\n![after-preserve](https://cloud.githubusercontent.com/assets/4430436/17122969/07acbb48-5295-11e6-9900-f9ba519affa4.gif)\n\nAdding a query parameter or controller property fixes this issue.\n\n\n### preserveScrollPosition with queryParams\n\nIn certain cases, you might want to have certain routes preserve scroll position when coming from a specific location.\nFor example, inside your application, there is a way to get to a route where the user expects scroll position to be\npreserved (such as a tab section).\n\n**1.** Add query param in controller\n\nAdd `preserveScrollPosition` as a queryParam in the controller for the route that needs to preserve the scroll position.\n\nExample:\n\n```javascript\nimport Controller from '@ember/controller';\n\nexport default class MyController extends Controller {\n  queryParams = [\n    'preserveScrollPosition',\n  ];\n}\n```\n\n**2.** Pass in query param\n\nNext, in the place where a transition is triggered, pass in `preserveScrollPosition=true`. For example\n\n```handlebars\n\u003cLinkTo \"About Tab\" \"tab.about\" {{query-params preserveScrollPosition=true}} /\u003e\n```\n\n\n### preserveScrollPosition with a controller property\n\nIn other cases, you may have certain routes that always preserve scroll position, or routes where the controller can\ndecide when to preserve scroll position. For instance, you may have some nested routes that have true nested UI where\npreserving scroll position is expected. Or you want a particular route to start off with the default scroll-to-top\nbehavior but then preserve scroll position when query params change in response to user interaction. Using a controller\nproperty also allows the use of preserveScrollPosition without adding this to the query params.\n\n**1.** Add query param to controller\n\nAdd `preserveScrollPosition` as a controller property for the route that needs to preserve the scroll position.\nIn this example we have `preserveScrollPosition` initially set to false so that we get our normal scroll-to-top behavior\nwhen the route loads. Later on, when an action triggers a change to the `filter` query param, we also set\n`preserveScrollPosition` to true so that this user interaction does not trigger the scroll-to-top behavior.\n\nExample:\n\n```javascript\nimport Controller from '@ember/controller';\nimport { action } from '@ember/object';\n\nexport default class MyController extends Controller {\n  queryParams = ['filter'];\n\n  preserveScrollPosition = false;\n\n  @action\n  changeFilter(filter) {\n    this.set('preserveScrollPosition', true);\n    this.set('filter', filter);\n  }\n}\n```\n\n**2.** Reset preserveScrollPosition if necessary\n\nIf your controller is changing the preserveScrollPosition property, you'll probably need to reset\n`preserveScrollPosition` back to the default behavior whenever the controller is reset. This is not necessary on routes\nwhere `preserveScrollPosition` is always set to true.\n\n```javascript\nimport Router from '@ember/routing/route';\n\nexport default class MyRoute extends Route {\n  resetController(controller) {\n    controller.set('preserveScrollPosition', false);\n  }\n}\n```\n\n\n### preserveScrollPosition via service\n\nYou may need to programatically control `preserveScrollPosition` directly from a component. This can be achieved by toggling the `preserveScrollPosition` property on the `routerScroll` service.\n\nOne common use case for this is when using query-param-based pagination on a page where `preserveScrollPosition` is expected to be false.\n\nFor example, if a route should always scroll to top when loaded, `preserveScrollPosition` would be false. However, a user may then scroll down the page and paginate through some results (where each page is a query param). But because `preserveScrollPosition` is false, the page will scroll back to top on each of these paginations.\n\nThis can be fixed by temporarily setting `preserveScrollPosition` to true on the service in the pagination transition action and then disabling `preserveScrollPosition` after the transition occurs.\n\nNote: if `preserveScrollPosition` is set to true on the service, it will override any values set on the current route's controller - whether query param or controller property.\n\n\n**1.** Manage preserveScrollPosition via service\n\nWhen you need to modify `preserveScrollPosition` on the service for a specific transition, you should always reset the value after the transition occurs, otherwise all future transitions will use the same `preserveScrollPosition` value.\n\nExample:\n\n```javascript\nimport Component from '@glimmer/component';\nimport { inject as service } from '@ember/service';\nimport { action } from '@ember/object';\n\nexport default class MyComponent extends Component {\n  @service routerScroll;\n  @service router;\n\n  @action\n  async goToPaginationPage(pageNumber) {\n    this.set('routerScroll.preserveScrollPosition', true);\n    await this.router.transitionTo(\n      this.router.currentRouteName,\n      {\n        queryParams: { page: pageNumber }\n      }\n    );\n\n    // Reset `preserveScrollPosition` after transition so future transitions behave as expected\n    this.set('routerScroll.preserveScrollPosition', false);\n  }\n}\n```\n\n## Running Tests\n\n* `npm test` (Runs `ember try:testall` to test your addon against multiple Ember versions)\n* `ember test`\n* `ember test --serve\n\nLicense\n------------------------------------------------------------------------------\n\nThis project is licensed under the [MIT License](LICENSE.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FDockYard%2Fember-router-scroll","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FDockYard%2Fember-router-scroll","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FDockYard%2Fember-router-scroll/lists"}