So my goal is to create an anchor link menu with Nuxt.js which scrolls to the page section the user has clicked and now I have few different ways of doing it but I'm not sure which is the "correct one".
Option 1:
I created a menu which gets the offsetTop
of the section div to know how far to scroll when the user clicks a link in the menu, but the problem I'm having with this is that few of the div's values such as offsetHeight
and offsetTop
change when I push the project to the server and I'm not sure what is causing it. For the result of this the link scrolls to the wrong place and does not work properly.
NOTE: I tried to swap places with sections Value and Problem and after that it works great but I still need to have the Value section on top of Problem.
Here I use created() to get the values and set them to the Vuex state and use the values in another component where the menu is located and all of it's functionality:
var elements = [
{name: 'Intro', el: document.getElementById('intro')},
{name: 'About', el: document.getElementById('about')},
{name: 'Process', el: document.getElementById('process')},
{name: 'Value', el: document.getElementById('value')},
{name: 'Problem', el: document.getElementById('problem')},
{name: 'Contact', el: document.getElementById('contact')}
]
var bodyScrollTop = document.body.scrollTop
elements.forEach((element) => {
console.log(element)
var name = element.name
var el = element.el
var posTop = el.offsetTop - bodyScrollTop
var elementHeight = el.offsetHeight
var posBottom = posTop + elementHeight
this.$store.state.windows.forEach((window) => {
if(window.id == name) {
window.posTop = posTop
window.posBottom = posBottom
console.log('PosTop', window.id, window.posTop)
console.log('PosBottom', window.id, window.posBottom)
}
})
})
I use window.scrollTo() to scroll to the section:
if (process.client) {
window.scrollTo({
top: position,
left: 0,
behavior: 'smooth'
});
}
Here's the div specs on the local machine with correct values:
accessKey: ""
align: ""
ariaAtomic: null
ariaAutoComplete: null
ariaBusy: null
ariaChecked: null
ariaColCount: null
ariaColIndex: null
ariaColSpan: null
ariaCurrent: null
ariaDescription: null
ariaDisabled: null
ariaExpanded: null
ariaHasPopup: null
ariaHidden: null
ariaKeyShortcuts: null
ariaLabel: null
ariaLevel: null
ariaLive: null
ariaModal: null
ariaMultiLine: null
ariaMultiSelectable: null
ariaOrientation: null
ariaPlaceholder: null
ariaPosInSet: null
ariaPressed: null
ariaReadOnly: null
ariaRelevant: null
ariaRequired: null
ariaRoleDescription: null
ariaRowCount: null
ariaRowIndex: null
ariaRowSpan: null
ariaSelected: null
ariaSetSize: null
ariaSort: null
ariaValueMax: null
ariaValueMin: null
ariaValueNow: null
ariaValueText: null
assignedSlot: null
attributeStyleMap: StylePropertyMap {size: 0}
attributes: NamedNodeMap {0: id, 1: class, 2: data-v-2a183b29, id: id, class: class, data-v-2a183b29: data-v-2a183b29, length: 3}
autocapitalize: ""
autofocus: false
baseURI: "http://localhost:3000/"
childElementCount: 2
childNodes: NodeList(3) [div.sidebar-content, text, div.section-content.problem-content]
children: HTMLCollection(2) [div.sidebar-content, div.section-content.problem-content]
classList: DOMTokenList(2) ['section', 'problem', value: 'section problem']
className: "section problem"
clientHeight: 766
clientLeft: 0
clientTop: 0
clientWidth: 1290
contentEditable: "inherit"
dataset: DOMStringMap {v-2a183b29: ''}
dir: ""
draggable: false
elementTiming: ""
enterKeyHint: ""
firstChild: div.sidebar-content
firstElementChild: div.sidebar-content
hidden: false
id: "problem"
innerHTML: "..."
innerText: "..."
inputMode: ""
isConnected: true
isContentEditable: false
lang: ""
lastChild: div.section-content.problem-content
lastElementChild: div.section-content.problem-content
localName: "div"
namespaceURI: "http://www.w3.org/1999/xhtml"
nextElementSibling: div#contact.section.contact
nextSibling: text
nodeName: "DIV"
nodeType: 1
nodeValue: null
nonce: ""
offsetHeight: 766
offsetLeft: 0
offsetParent: div.v-application--wrap
offsetTop: 3390
offsetWidth: 1290
onabort: null
onanimationend: null
onanimationiteration: null
onanimationstart: null
onauxclick: null
onbeforecopy: null
onbeforecut: null
onbeforepaste: null
onbeforexrselect: null
onblur: null
oncancel: null
oncanplay: null
oncanplaythrough: null
onchange: null
onclick: null
onclose: null
oncontextmenu: null
oncopy: null
oncuechange: null
oncut: null
ondblclick: null
ondrag: null
ondragend: null
ondragenter: null
ondragleave: null
ondragover: null
ondragstart: null
ondrop: null
ondurationchange: null
onemptied: null
onended: null
onerror: null
onfocus: null
onformdata: null
onfullscreenchange: null
onfullscreenerror: null
ongotpointercapture: null
oninput: null
oninvalid: null
onkeydown: null
onkeypress: null
onkeyup: null
onload: null
onloadeddata: null
onloadedmetadata: null
onloadstart: null
onlostpointercapture: null
onmousedown: null
onmouseenter: null
onmouseleave: null
onmousemove: null
onmouseout: null
onmouseover: null
onmouseup: null
onmousewheel: null
onpaste: null
onpause: null
onplay: null
onplaying: null
onpointercancel: null
onpointerdown: null
onpointerenter: null
onpointerleave: null
onpointermove: null
onpointerout: null
onpointerover: null
onpointerrawupdate: null
onpointerup: null
onprogress: null
onratechange: null
onreset: null
onresize: null
onscroll: null
onsearch: null
onseeked: null
onseeking: null
onselect: null
onselectionchange: null
onselectstart: null
onstalled: null
onsubmit: null
onsuspend: null
ontimeupdate: null
ontoggle: null
ontouchcancel: null
ontouchend: null
ontouchmove: null
ontouchstart: null
ontransitioncancel: null
ontransitionend: null
ontransitionrun: null
ontransitionstart: null
onvolumechange: null
onwaiting: null
onwebkitanimationend: null
onwebkitanimationiteration: null
onwebkitanimationstart: null
onwebkitfullscreenchange: null
onwebkitfullscreenerror: null
onwebkittransitionend: null
onwheel: null
outerHTML: "..."
ownerDocument: document
parentElement: div#cridty
parentNode: div#cridty
part: DOMTokenList [value: '']
prefix: null
previousElementSibling: div#value.section.value
previousSibling: text
scrollHeight: 766
scrollLeft: 0
scrollTop: 0
scrollWidth: 1290
shadowRoot: null
slot: ""
spellcheck: true
style: CSSStyleDeclaration {accentColor: '', additiveSymbols: '', alignContent: '', alignItems: '', alignSelf: '', …}
tabIndex: -1
tagName: "DIV"
textContent: "..."
title: ""
translate: true
virtualKeyboardPolicy: ""
[[Prototype]]: HTMLDivElem
Here's the object the scroll() function used on local machine in the menu component to define where to scroll and when the link is active and when it is not:
active: (…)
hash: (…)
id: (…)
posBottom: 4128
posTop: 3385
And here's the div specs on Ubuntu server with changed values:
accessKey: ""
align: ""
ariaAtomic: null
ariaAutoComplete: null
ariaBusy: null
ariaChecked: null
ariaColCount: null
ariaColIndex: null
ariaColSpan: null
ariaCurrent: null
ariaDescription: null
ariaDisabled: null
ariaExpanded: null
ariaHasPopup: null
ariaHidden: null
ariaKeyShortcuts: null
ariaLabel: null
ariaLevel: null
ariaLive: null
ariaModal: null
ariaMultiLine: null
ariaMultiSelectable: null
ariaOrientation: null
ariaPlaceholder: null
ariaPosInSet: null
ariaPressed: null
ariaReadOnly: null
ariaRelevant: null
ariaRequired: null
ariaRoleDescription: null
ariaRowCount: null
ariaRowIndex: null
ariaRowSpan: null
ariaSelected: null
ariaSetSize: null
ariaSort: null
ariaValueMax: null
ariaValueMin: null
ariaValueNow: null
ariaValueText: null
assignedSlot: null
attributeStyleMap: StylePropertyMap {size: 0}
attributes: NamedNodeMap {0: id, 1: class, 2: data-v-dcd49e84, id: id, class: class, data-v-dcd49e84: data-v-dcd49e84, length: 3}
autocapitalize: ""
autofocus: false
baseURI: "..."
childElementCount: 2
childNodes: NodeList(3) [div.sidebar-content, text, div.section-content.problem-content]
children: HTMLCollection(2) [div.sidebar-content, div.section-content.problem-content]
classList: DOMTokenList(2) ['section', 'problem', value: 'section problem']
className: "section problem"
clientHeight: 748
clientLeft: 0
clientTop: 0
clientWidth: 1290
contentEditable: "inherit"
dataset: DOMStringMap {vDcd49e84: ''}
dir: ""
draggable: false
elementTiming: ""
enterKeyHint: ""
firstChild: div.sidebar-content
firstElementChild: div.sidebar-content
hidden: false
id: "problem"
innerHTML: "..."
inputMode: ""
isConnected: true
isContentEditable: false
lang: ""
lastChild: div.section-content.problem-content
lastElementChild: div.section-content.problem-content
localName: "div"
namespaceURI: "http://www.w3.org/1999/xhtml"
nextElementSibling: div#contact.section.contact
nextSibling: text
nodeName: "DIV"
nodeType: 1
nodeValue: null
nonce: ""
offsetHeight: 748
offsetLeft: 0
offsetParent: div.v-application--wrap
offsetTop: 3340
offsetWidth: 1290
onabort: null
onanimationend: null
onanimationiteration: null
onanimationstart: null
onauxclick: null
onbeforecopy: null
onbeforecut: null
onbeforepaste: null
onbeforexrselect: null
onblur: null
oncancel: null
oncanplay: null
oncanplaythrough: null
onchange: null
onclick: null
onclose: null
oncontextmenu: null
oncopy: null
oncuechange: null
oncut: null
ondblclick: null
ondrag: null
ondragend: null
ondragenter: null
ondragleave: null
ondragover: null
ondragstart: null
ondrop: null
ondurationchange: null
onemptied: null
onended: null
onerror: null
onfocus: null
onformdata: null
onfullscreenchange: null
onfullscreenerror: null
ongotpointercapture: null
oninput: null
oninvalid: null
onkeydown: null
onkeypress: null
onkeyup: null
onload: null
onloadeddata: null
onloadedmetadata: null
onloadstart: null
onlostpointercapture: null
onmousedown: null
onmouseenter: null
onmouseleave: null
onmousemove: null
onmouseout: null
onmouseover: null
onmouseup: null
onmousewheel: null
onpaste: null
onpause: null
onplay: null
onplaying: null
onpointercancel: null
onpointerdown: null
onpointerenter: null
onpointerleave: null
onpointermove: null
onpointerout: null
onpointerover: null
onpointerrawupdate: null
onpointerup: null
onprogress: null
onratechange: null
onreset: null
onresize: null
onscroll: null
onsearch: null
onseeked: null
onseeking: null
onselect: null
onselectionchange: null
onselectstart: null
onstalled: null
onsubmit: null
onsuspend: null
ontimeupdate: null
ontoggle: null
ontouchcancel: null
ontouchend: null
ontouchmove: null
ontouchstart: null
ontransitioncancel: null
ontransitionend: null
ontransitionrun: null
ontransitionstart: null
onvolumechange: null
onwaiting: null
onwebkitanimationend: null
onwebkitanimationiteration: null
onwebkitanimationstart: null
onwebkitfullscreenchange: null
onwebkitfullscreenerror: null
onwebkittransitionend: null
onwheel: null
outerHTML: "..."
ownerDocument: document
parentElement: div#cridty
parentNode: div#cridty
part: DOMTokenList [value: '']
prefix: null
previousElementSibling: div#value.section.value
previousSibling: text
scrollHeight: 748
scrollLeft: 0
scrollTop: 0
scrollWidth: 1290
shadowRoot: null
slot: ""
spellcheck: true
style: CSSStyleDeclaration {accentColor: '', additiveSymbols: '', alignContent: '', alignItems: '', alignSelf: '', …}
tabIndex: -1
tagName: "DIV"
textContent: "..."
title: ""
translate: true
virtualKeyboardPolicy: ""
[[Prototype]]: HTMLDivElem
And this is the console log of the object the scroll() function used on the server in the menu component to define where to scroll and when the link is active and when it is not:
active: (…)
hash: (…)
id: (…)
posBottom: 3484
posTop: 2736
It only the one section which does not work, every other works great.
Option 2:
The second way I tried to make it work was with anchor links and I got it to scroll to the right section when clicking a link in the menu but now the problem is that I need to change the hash is the url when the user scrolls to a section. I tried to change the url with this.$router.push({path: element.hash})
. The url changes when the user entries the section but the problem is that every time $router.push runs, the scrolling experience becomes very bad because it tries to scroll back to the section while the user is scrolling down to a new section.
Now I'm jumping between these two and don't know which one would be the better option to invest my time in.