anchor tag <a id="jump"> with hash inside shadow dom
Asked Answered
T

2

10

I would like to use an anchor-element with a hash-URL inside of a custom element that uses shadow DOM. I would expect, that the browser scrolls down to that anchor, but it does not do it (at least Chrome).

Detail:

I have an index.html like this:

...
<a href="#destinationInsideShadowDOM">Jump</a>
...
<my-custom-element></my-custom-element>
...

And another html-file for the custom-element, which contains the anchor:

<template id="my-custom-element">
   ...
   <a id="destinationInsideShadowDOM"></a>
   ...
</template>

I want the browser to scroll down to that anchor when I click on the link in index.html.

When I move the anchor into index.html, outside of the custom-element, it works, but not inside.

Thorazine answered 15/4, 2017 at 11:18 Comment(4)
By definition Shadow Dom isolates it's content from the main Dom tree for all selections (that is: for CSS but also for querySelector and links). So it' the normal, expected behaviour. You'll have to handle this by JSMoleskins
I understand. I solved it using scrollIntoView()Thorazine
I'm still having problems when opening the hashed link in a new browser tab. I'm currently using a scrollIntoView() in a setTimeout inside Polymer callbacks (either ready or connectedCallback), but it only works around half the times. It seems that the template just isn't stamped in the DOM by the time the callback fires. One idea I've had to solve this is to use the MutationObserver API to scroll once the content has loaded, but it seems like overkill.Komarek
I use vanilla custom Elements without polymer, so maybe there are some differences that I don't know, but using a timeout should't be nessecary. Did you try to use the WebComponentsReady-Event? That is fired by the polyfills that are used by polymer. See #21764190Thorazine
M
1

Go into the console and type this:

document.getElementById('destinationInsideShadowDOM')

The result will be null because the element with that ID does not belong to the document. It belongs to the shadowRoot of the custom element.

As @treeno mentioned in a comment, you can use Element.scrollIntoView() to get the behavior you want.

Municipality answered 31/10, 2022 at 19:20 Comment(11)
Could you elaborate on how to use Element.scrollIntoView in this precise example? This means digging through the shadow DOM in the link "onclick" event handler to access the element that must be scrolled into view?Apeak
Yes, something like document.querySelector(‘my-custom-element’).shadowRoot.getElementById(‘destinationInsideShadowDom’).scrollIntoView()Municipality
In practice if I know I’m going to want to move some particular tag in my custom element into view, I’d probably create a method on the custom element to do that. In other words, make it part of the element’s public API and preserve encapsulation so I don’t have to dig into the shadow DOM from outside. And I don’t have to worry that if I refactor I may break someone else’s code.Municipality
Very interesting, do you have any example/pointer to some docs? I am not sure how to create such a public API, to me the custom element is mostly HTML from an external standpoint, not sure how to add a new executable method to it, apart from triggering custom events.Apeak
A custom element is defined by a class that extends HTMLElement. You can add methods and properties to that class. It will be easier to answer if you post a new question with a snippet of your code and ask “how can I add methods to the custom element in this code”.Municipality
I feel like the current question is pretty relevant as a use case actually, and it's not properly answered. I get how to add a method to a custom element, but I don't get how you would properly implement scrolling this way, because the component that receive the click might be a parent of the element that should be scrolled, or they might be totally unrelated. So who calls what when? Currently I dig the shadow DOM and call scrollIntoView but it feels bristle.Apeak
Yes, linking to the inside of an element's shadow DOM is inherently brittle. You could add a public method to your element like scrollToMe() { this.shadowRoot.querySelector('#innerElement').scrollIntoView() }. I could elaborate more in an answer instead of a comment, especially if I knew more details about your specific use case. Why not ask another question? Questions are free.Municipality
What I don't get is where/how would you call "theElement.scrollToMe()"? In the example provided by OP, you would replace the "a" tag by a button with an "click" handler, but it then how it gets the right element to scroll to? I've posted an answer using Lit below, tracking the element via a ref, but I don't think this would allow to call a method internal to this element (or not sure how if it's actually possible).Apeak
Ok after more research, you can just call the element custom methods, so document.querySelector("my-component").scrollToMe() would work for instance.Apeak
Yes, exactly. I still think it makes sense to ask as a separate question. Even if you answer it yourself. Surface that knowledge to help the next person. :)Municipality
Trying not to create dupes, I've searched with a few keywords, I think this one could be relevant: #73714784 I am also trying to look for a place to document this, after a few months journey in the web components world I barely found docs recalling those simple facts or pointings to docs that describe themApeak
A
0

Using Lit, a ref allows to keep track of the elements.

// rough pseudo-code
class Parent extends LitElement {

this.refs: Map<Ref>

constructor() {
  this.refs = new Map()
}

render() {
  const elems = this.ids.map((id) => {
    const elemRef = createRef<HTMLElement>()
    return = html`<div id=${id} ${ref(elemRef)}`
  })
  return html`<div>
<button @click=${() => this.refs.get("1").scrollIntoView()}>Scroll<button>
${elems}
</div>`
}

}
  • It's using Lit, not raw web components
  • The parent has to know about the child you want to scroll to. You may end up needing some additional logic if the parent is not aware of those child.
Apeak answered 24/4, 2023 at 9:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.