I am trying to implement a menu with menu items scrolling to sections ids.
It works fine if I scroll on a section in the page that is on the page where I am currently.
To achieve it I installed vue-scrollto.
The problem is that the same menu is on other pages too, meaning that in that case it should load the page where the section is and scroll to it.
To give you an idea, it's a website previously done in jQuery the I am redoing in Nuxt. In jQuery I wass solving it with:
$('html,body').animate({
scrollTop: $(window.location.hash).offset().top -60
}, 1000);
So far the closest I got is:
<li class="nav-item"><nuxt-link to="/" v-scroll-to="{ element: '#intro', duration: 1000, offset: -100 }" >Welcome</nuxt-link></li>
But if for instance, I am in the about page, first it loads the homepage and if I click again it scrolls to the section. How can I make work without having to click the menu link twice?
I had a similar use case in Nuxt. I achieved using the following (although I didn't use the plugin you referred to):
~/app/router.scrollBehavior.js
export default async function (to, from, savedPosition) {
if (savedPosition) {
return savedPosition
}
const findEl = (hash, x) => {
return document.querySelector(hash) ||
new Promise((resolve, reject) => {
if (x > 50) {
return resolve()
}
setTimeout(() => { resolve(findEl(hash, ++x || 1)) }, 100)
})
}
if (to.hash) {
const el = await findEl(to.hash)
if ('scrollBehavior' in document.documentElement.style) {
return window.scrollTo({ top: el.offsetTop, behavior: 'smooth' })
} else {
return window.scrollTo(0, el.offsetTop)
}
}
return { x: 0, y: 0 }
}
You can find more information about configuring Nuxt router and its scroll behaviour here.
Related
I have a problem using Quasar with VueJs.
When I use Quasar's Loading.show() method, the scroll is stuck when I go to the new page, and the top of the new page is not shown.
In the router/index.js file i have set
const router = new VueRouter({
scrollBehavior: () => ({ x: 0, y: 0}),
...
});
For example:
In MainLayout.vue (which is the parent component of the index and category page), I have set a watch to show the loading screen when the isLoading flag is true in the state.
watch: {
isLoading: {
deep: true,
handler(isLoading) {
if(isLoading == true) {
Loading.show();
} else {
Loading.hide();
}
}
}
}
This is working fine, but when I have clicked a link to a category page after I have scrolled a little bit, The category page will also retain the previous scroll position.
Is working without the scroll issue with commented out Loading.show();
Video from the issue:
https://drive.google.com/file/d/1d-WaxHazNdZ8bp3_pcN14hcayucUYYUb/view
Try to do this, but I'm not sure if this is the right solution:
import { scroll } from 'quasar'
const { getScrollTarget, setVerticalScrollPosition } = scroll
watch: {
isLoading: {
deep: true,
handler(isLoading) {
if(isLoading == true) {
Loading.show();
} else {
Loading.hide();
const el = document.querySelector('.q-page-container')
const target = getScrollTarget(el)
const offset = el.offsetTop
const duration = 0
setTimeout(() => setVerticalScrollPosition(target, offset, duration), 400);
}
}
}
}
Documentation https://quasar.dev/quasar-utils/scrolling-utils#scrolling-to-an-element
I have created a file app/router.scrollBehavior.js for scroll position, but when I press browser back button, savedPosition doesn't call because fetch api has not completed. Can any one help me for this. How to know when api's completion done after my savePosition trigger otherwise every time scroll got on bottom position. is ther any way to get store, commit fetch hooks in same functions.
export default function (to, from, savedPosition) { if (savedPosition) { return savedPosition } else { return { x: 0, y: 0 } } }
You can use async scrolling, that resolves to the desired position descriptor.
It's possible to hook this up with events from a page-level transition component to make the scroll behavior play nicely with your page transitions.
scrollBehavior (to, from, savedPosition) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ x: 0, y: 0 })
}, 500)
})
}
I'm looking for a smart way to handle in-page anchors with Vue Router. Consider the following:
<router-link to="#app">Apply Now</router-link>
<!-- some HTML markup in between... -->
<div id="app">...</div>
The "scroll to anchor" behavior described in the docs works fine except:
When you click on the anchor, it brings you down to the div id="app". Now scroll away from the div back to the anchor and try clicking it again -- this time you will not jump down to the div. In fact, the anchor will retain the class router-link-active and the URL will still contain the hash /#app;
With the steps above, if you refresh the page (the URL will still contain the hash) and click on the anchor, nothing will happen either.
This is very unfortunate from the UX perspective because a potential customer has to manually scroll all the way down again to reach the application section.
I was wondering if Vue Router covers this situation. For reference, here's my router:
export default new VueRouter({
routes,
mode: 'history',
scrollBehavior(to, from, savedPosition) {
if (to.hash) {
return { selector: to.hash }
} else if (savedPosition) {
return savedPosition;
} else {
return { x: 0, y: 0 }
}
}
})
I haven't found anything in the resources to solve your issue but you could utitlize the $route.hash in your mounted hook of the component that holds your <router-view></router-view> to solve the refresh issue.
<script>
export default {
name: 'app',
mounted: function()
{
// From testing, without a brief timeout, it won't work.
setTimeout(() => this.scrollFix(this.$route.hash), 1);
},
methods: {
scrollFix: function(hashbang)
{
location.hash = hashbang;
}
}
}
</script>
Then to solve the issue of second clicks you could use the native modifier and bind to your <router-link></router-link>. It's a fairly manual process but will work.
<router-link to="#scroll" #click.native="scrollFix('#scroll')">Scroll</router-link>
There may also be something you could do with the router's afterEach method but haven't figured that out yet.
If you're already on the route with the hash, you can just set it to scroll to the target.
(also note scrollBehavior() method in your router won't get called if you're already on the route you're trying to go to).
export default {
methods: {
anchorHashCheck() {
if (window.location.hash === this.$route.hash) {
const el = document.getElementById(this.$route.hash.slice(1))
if (el) {
window.scrollTo(0, el.offsetTop)
}
}
},
},
mounted() {
this.anchorHashCheck()
},
}
Then add a #click.native to listen to events on the anchor in your <router-link>,
<router-link :to="{hash: '#some-link'}" #click.native="anchorHashCheck">
Some link
</router-link>
Possible solution which is more resusable IMO:
this.$router.push({ name: 'home' }, undefined, () => { location.href = this.$route.hash })
As the 3rd argument is the abort() function, it may have unwanted side effects though..
If you want to use it globally, add a function to your Router:
pushWithAnchor: function (routeName, toHash) {
const fromHash = Router.history.current.hash
fromHash !== toHash || !fromHash
? Router.push({ name: routeName, hash: toHash })
: Router.push({ name: routeName, hash: fromHash }, undefined, () => { window.location.href = toHash })
}
And use it in components with:
this.$router.options.pushWithAnchor('home', '#fee-calculator-section')
Within a template you could do something like:
<a #click="this.$router.options.pushWithAnchor('home', '#fee-calculator-section')"></a>
Sadly you cant use a scroll offset though
I used this solution:
<router-link to="/about" #click.native="scrollFix('#team')" >The team</router-link>
And this:
methods: {
scrollFix: function(hash) {
setTimeout(() => $('html, body').animate({
scrollTop: $(hash).offset().top
}, 1000), 1)
}
}
I realize you asked about anchors and you have an answer already. However, the below function should work for both anchors and regular links. It allows you to scroll to the position of the first instance of the Component a Route has been matched to. I wrote it to see if I could by-pass using a hash while retaining anchor-style scrolling.
scrollBehavior(to, from, savedPosition) {
if (to.matched) {
const children = to.matched[1].parent.instances.default.$children;
for (let i = 0; i < children.length; i++) {
let child = children[i];
if (child.$options._componentTag === to.matched[1].components.default.name) {
return {
x: child.$el.offsetLeft,
y: child.$el.offsetTop
};
}
}
}
return {
x: 0,
y: 0
};
}
The reason I'm using parent.instances is because the to.matched[1].instances value is empty. It's not the most elegant solution, though it might help someone else out there.
Note: This only works when you want to scroll the first instance of a Component.
I have a small Vue.js SPA with the following router configuration, taken from the docs:
export default new VueRouter({
routes, // defined above...
mode: 'history',
scrollBehavior(to, from, savedPosition) {
if (to.hash) {
return { selector: to.hash }
} else if (savedPosition) {
return savedPosition;
} else {
return { x: 0, y: 0 }
}
}
})
Consider a link on the homepage:
<router-link to="#services">Services</router-link>
It jumps to the anchor element <div id="services">...</div> as expected. However, when you activate the link, then scroll away from #services, and refresh the page, you will not be brought back to #services. You will stay in the same position where you left off, even though the URL would still have the hash in it (e.g. in the form of app.dev/#services).
How can I configure the router so that on page load, it bring the user to the anchor element, given that the URL contains its hash (and, well, that hash corresponds to a valid existing element)?
I had the same problem but also wanted to have an animated scroll to the hash. I was able to check off both features with vue-scrollto.
https://github.com/rigor789/vue-scrollto
Something like this should work.
// import
import VueScrollTo from 'vue-scrollto';
//...
scrollBehavior(to, from, savedPosition) {
if (to.hash) {
VueScrollTo.scrollTo(to.hash, 700);
return { selector: to.hash }
} else if (savedPosition) {
return savedPosition;
} else {
return { x: 0, y: 0 }
}
}
That way, it always animates to the hash.
If you don't want to animate, use 0 as the time instead of 700.
If you don't like using that module, you can manually jump to the anchor via regular javascript using the techniques described here
I tried this code:
var clearDamnSpan = setInterval(function () {
$('body > span').each(function () {
if ($(this).text() === 'BESbewy') {
//if you remove the element, You might see errors in Google Chrome
$(this).hide();
clearInterval(clearDamnSpan);
}
});
}, 500);
setTimeout(function () {
clearInterval(clearDamnSpan);
}, 50000);
it didn't work. so what do you suggest me to do?
use the word "BESbswy" instead of "BESbewy" in the code above.