1

Description of the setup

I'm building a website for a DnD campaign of mine in Django and using a modified version of this HTML5up.net page (CSS and JS is unchanged from stock). In my setup I have 2 pages, an index page (the HTML5up one) that people land on and "Character" pages that are hyperlinked to in the index page.

A jsfiddle to demonstrate the index page (without images) is here.

At the start the index page displays only the <header>, while the hyperlinks are in the <article id="characters"> section, which is not visible in the beginning. These sections only become visible through clicking on their respective button e.g. <a href="#characters">Characters</a> which jumps to the respective section.

My goal:

I want to be able to jump to not just the <article> section, but I want to be able to open the <article> AND jump to a specific hyperlink within it (e.g. the link to the character-page of "Zart"). With this I could create a button on character pages that lead back to the index page at exactly the position the user left the index page at.

My Problem :

Hyperlinking back to index and anchor jumping to an <article> section (e.g. "characters") is easily doable with this :

<a href="LinkOfIndexPagePlaceholder#characters"> Back to Characters </a>

Hyperlinking back to index in a way that opens the article section AND jumps to the specific character (e.g. "Zart") in an article does not work with this, despite the anchor of an item (e.g. "Zart") having also the correct id (e.g. "Zart"):

<a href="LinkOfIndexPagePlaceholder#Zart"> Back to Characters </a>

In fact, I am entirely unable to anchor jump to the anchor with id "Zart" at all, as long as the article section of "character" isn't already being displayed.

If it is already being displayed (tested by changing the href of the "Add Character" Button in the jsfiddle to "#Zart") then I can jump to to that anchor, so the general way of jumping works. It is jumping to anchors that aren't being displayed (so in CSS "display:none") that is problematic. I need to somehow get that article section to be displayed before jumping.

How do I get it to work?

What I tried so far were the approaches discussed here. None of them worked for me. The first answer lead to the text and hyperlinks of all article sections to disappear, the second was already what I was trying to do.

Resources

Below is a shortened version of the HTML of the page.

I shortened it down to essential bits for the problem, so only a single entry and only the character section remain. To get the CSS, please download the page either from HTML5up or get its css files from filebin as zip. They are sadly too long to post here and I don't know which bits are the essential ones for this.

The HTML of the index page :

<!DOCTYPE HTML>
<!--
    Dimension by HTML5 UP
    html5up.net | @ajlkn
    Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
    -->
<html>
<head>
    <title> Aldrune </title>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
          integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"/>
    <link rel="stylesheet" href="{% static 'frontpage/assets/css/main.css' %}"/>
    <link rel="stylesheet" href="{% static 'fontawesome47/css/font-awesome.min.css' %}">

    <noscript>
        <link rel="stylesheet" href="{% static 'frontpage/assets/css/noscript.css' %}"/>
    </noscript>
</head>
<body class="is-preload">

<!-- Wrapper -->
<div id="wrapper">

    <!-- Header -->
    <header id="header">
        <div class="logo">
            <a class="icon"><span class="icon fa-gem"> </span></a>
        </div>
        <div class="content">
            <div class="inner">
                <h1>Aldrune</h1>
                <p><b> A campaign made by X and recorded by Y</b></p>
            </div>
        </div>
        <nav>
            <ul> <!-- The anchor-jump buttons to <article> -->
                <li><b><a href="#characters">Characters</a></b></li>
            </ul>
        </nav>
    </header>

    <!-- Main -->
    <div id="main">

        <!-- Characters article -->
        <article id="characters">
            <h2 class="major">Characters</h2>
            <ul> 
                <li><a id="Zart" href="LinkToCharactersPage1"> Zart </a></li>
                <li><a id="Zenevieva" href="LinkToCharactersPage2"> Zenevieva </a></li>
                <li><a id="Zuxus" href="LinkToCharactersPage3"> Zuxus </a></li>
            </ul>
        </article>
    </div>
</div>

<!-- Scripts -->
<script src="{% static 'frontpage/assets/js/jquery.min.js' %}"></script>
<script src="{% static 'frontpage/assets/js/browser.min.js' %}"></script>
<script src="{% static 'frontpage/assets/js/breakpoints.min.js' %}"></script>
<script src="{% static 'frontpage/assets/js/util.js' %}"></script>
<script src="{% static 'frontpage/assets/js/main.js' %}"></script>

</body>
</html>
Philipp Doerner
  • 1,090
  • 7
  • 24

3 Answers3

2

It is possible with a bit js-wizardry. Basic principle is to set the element visible to get the required values to compute the offset. After doing so, the element will be set to its original state. - No worries the element WILL NOT flash for a short time. The content will be re-rendered after the js ran through.

This script will need to run after the page has been rendered, so place js at bottom OR wrap it with window.addEventListener('load', function() { /* Code here... */ });

// Pick every element having the class "js-jump"
document.querySelectorAll('.js-jump').forEach(function(element) {
  // Handle the "onclick" event for each given element
  element.onclick = function() {
    // read "data-target" of element to select the target
    const target = document.querySelector(element.dataset.target);
    
    // set the element you want to scroll
    // const scrollArea = document.window; // <- If you want to scroll the whole frame
    const scrollArea = document.querySelector('.scrollArea');
    
    // save the original "display" state of your element (to restore it, as soon we are done)
    const originalTargetDisplay = target.style.display;
    
    // "show" the target to get required values
    target.style.display = 'inline-block';
    
    // get the distance to top
    offset = target.offsetTop;
    offset -= Math.ceil(target.offsetHeight / 2);
    
    // restore original display-attribute
    target.style.display = originalTargetDisplay;

    // scroll :)
    scrollArea.scrollTop = offset;
  }
});
.scrollArea {
  border: 1px solid black;
  padding: 15px;
  height: 50px;
  overflow: scroll;
}

#a-tag {
  display: none;
}
<div class="scrollArea">
  Some<br />Content<br />Content<br />Content<br />Content<br />Content<br />
  <br />
  Hidden A-Tag -> <a id='a-tag'>I am hidden!</a> <- here!
  <hr />
  Some<br />More<br />Content<br />Content<br />Content<br />Content<br />
  Content<br />Content<br />Content<br />Content<br />
</div>
<hr / >
Controls:<br />
<a href='#a-tag'>Jump via LINK</a> (no working)<br/>
<a href='#' data-target='#a-tag' class='js-jump'>Jump via JS</a><br />
SirPilan
  • 4,649
  • 2
  • 13
  • 26
  • As someone with no proper JS knowledge worth mentioning, I assume the target is specified using the "data-target" attribute in the tag? How/where in the JS code does the content of that tag reach the JS function? Is the code executed upon startup or triggered by an event ? This demonstrates a jump from within a page, would this still work if I were on an entirely different HTML page (the character pages are individual HTML pages that have their own URLs etc.) and linking back to the old one where I want to perform the jump? – Philipp Doerner Jun 26 '20 at 14:01
  • 1
    This is just the basic principle. You cant copy/paste this without tweaking it. For example i hardcoded the "scrollArea" for demonstation. Normally you want to scroll the document.window. I'll add some comments to answer your questions. – SirPilan Jun 26 '20 at 14:07
  • Thank you so much. I thought as much, just getting a bit more of an idea on what more explicitly I need to learn about to be able to apply this will make things so much easier in terms of learn-time. – Philipp Doerner Jun 26 '20 at 14:11
  • Finally had some time to take a closer look at it. I think I get the overall gist now. Essentially we're scrolling to the target. 1) Get the "data-target" value of the clicked element as that defines the id of the target element to which you want to jump. 2) Define inside of which element in the DOM you want to scroll 3) temp-store current scroll-location for restoration afterwards 4) Set the target element to visible and calculate scroll-distance (Distance from top of scrolled element to middle of the target-element) 5) Set Element invisible again 6) Scroll down with calculated offset – Philipp Doerner Jul 04 '20 at 13:40
  • Now if I understand correctly, the start defines that the jump-code shall be triggered as an on-click event for elements with that class. Now I'd want to jump with a button on site A to site B and directly jump down. Thus I would want to trigger a script on site B similar to yours automatically depending on if the initial page was loaded with a normal url or a "#" at the end. Thus I should look into JS ways to read in the URL to go "If "#" in URL then execute jump-code else do nothing". I then use that to trigger the code as opposed to an on-click event. Is that correct? – Philipp Doerner Jul 04 '20 at 13:49
  • Just have to correct 3) We save to current 'display' attribute (because the element may already be visible), if we just hide it at the end, we would change the document, but we just want to set it temporarily to 'display: inline-block' to calculate height. And set it to its original state afterwards.4) We already get the middle of the target, but we want to be above the target, so we reduce the scroll distance by half of the height of the target. 5) restore original 'display' saved in 3 – SirPilan Jul 04 '20 at 16:31
  • 1
    To scroll on load you will need to have the target in your url (myPage.com#myTarget). You receive this by parsing `window.location.href`. The rest is pretty much the same as above :) – SirPilan Jul 04 '20 at 16:34
  • I got it to work using this approach, though I had to go through a couple extra-hoops due to the convoluted nature of the HTML design that I... *ahem* appropriated from HTML5up and its own JS scripts. I also couldn't get the approach of scrolling an Element to work for the life of me, so I used "window" from the DOM with "scrollBy", which finally did the trick. – Philipp Doerner Jul 05 '20 at 07:25
1

Thanks to @Pilan's explanation and some diving into JS I managed to build a solution for this. That, and because his solution is the more generalized one, is the reason I accepted his answer as the answer to this question.

This solution is based on Pilan's approach, but customized with the extra-steps necessary for what I wanted to achieve. Thus, this is for anyone also working with this exact HTML design (Dimension) from HTML5up.

In the end I attached this piece of javascript to the HTML:

//I have 3 possible urls to expect: 
// 1) http://localhost:8000/wiki/ - Open wiki page
// 2) http://localhost:8000/wiki/#characters - Open wiki page and display otherwise hidden article element with that Id ('characters') on wiki page
// 3) http://localhost:8000/wiki/#characters#Mruvain - Open wiki page, display article  ('characters') and scroll to element with that id ('Mruvain) in that article
// Only in case of url 3) do I want to scroll

// get url as string, e.g. http://localhost:8000/wiki/#characters#Mruvain
const url = window.location.href;

// Check if we are in scenario 2) or 3) by checking if URL includes "#"
if (url.includes('#')){
    // split the url into an array, in scenario 3 we will have the article id in index 1 and target-element-id in index 2
    const hash = url.split('#');
    // Checks if we are in scenario 3) by checking if hash has exactly 3 elements
    if (hash.length == 3) {
        //Get the article element
        const article_id = hash[1];
        const article = document.getElementById(article_id);

        //Get the target element
        const target_id = hash[2];
        const target = document.getElementById(target_id);

        // Hide the initially displayed part of the page
        document.getElementById('header').style.display='none';
        // Make background blurry as it is when an article is open
        document.body.className = 'is-article-visible';
        // Display the background frame for the article to display
        document.getElementById('main').style.display='flex';
        // Set the article as active to display it
        article.className = 'active';
        article.style.display='block';

        // The element is now visible since the entire article is visible. Calculate it's distance to the top 
        offset = target.offsetTop;
        offset -= Math.ceil(target.offsetHeight / 2);
        // Scroll the window on the Y-axis for that many pixel downwards
        window.scrollBy(0, offset);
    }
}
Philipp Doerner
  • 1,090
  • 7
  • 24
0

I believe linking to a hidden element is not possible because it doesnt contain the neccessary css properties to calculate the positioning. There are multiple ways to hide something without using display: none;.

For example: overflow: hidden; height: 0;

In order to change the styling based on a click event in order to show the content is only possible with JS i believe. You could so some very hacky stuff with css selectors and input:active status, but i do not recommend that with elements that are so far seperated in the DOM.

Do you try to avoid JS for this functionality or what you approach?

Jan-Philipp Marks
  • 1,419
  • 8
  • 13
  • Me avoiding js is a matter of experience. I'm a python developer, this is my first foray into web development. HTML and CSS are on their most basic level easy to pick up on the go, js requires a more dedicated mindset. I did plan on learning it, just not for my first project immediately, so I wanted to avoid it. I take it there's no getting around that one though (with clean code) if I want to get this to work the way I want. Darn. – Philipp Doerner Jun 26 '20 at 13:09
  • Do you mayhaps have some recommendations of what I have to learn in JS to do this? And what the general rough architecture of the solution would look like ? – Philipp Doerner Jun 26 '20 at 13:22
  • I would recommend starting here for any DOM manipulation: https://www.w3schools.com/js/js_htmldom.asp – Jan-Philipp Marks Jul 05 '20 at 08:08