Make anchor links refer to the current page when using <base>
Asked Answered
F

13

67

When I use the HTML <base> tag to define a base URL for all relative links on a page, anchor links also refer directly to the base URL. Is there a way to set the base URL that would still allow anchor links to refer to the currently open page?

For example, if I have a page at http://example.com/foo/:


Current behaviour:

<base href="http://example.com/" />
<a href="bar/">bar</a> <!-- Links to "http://example.com/bar/" -->
<a href="#baz">baz</a> <!-- Links to "http://example.com/#baz" -->

Desired behaviour:

<base href="http://example.com/" />
<a href="bar/">bar</a> <!-- Links to "http://example.com/bar/" -->
<a href="#baz">baz</a> <!-- Links to "http://example.com/foo/#baz" -->
Foamflower answered 13/11, 2011 at 1:16 Comment(6)
Are you using a server side programming language? You could dynamically inline the current request URI in the link. See also #1889576Pentecostal
@Pentecostal I'm not, and I'd rather avoid it if possible.Foamflower
Well, if everything is already static like that, then just use <a href="foo#baz">.Pentecostal
I have to say this is so wrong, you set the base to http://server/ and you tell it to navigate to #baz, it will go http://server/#baz, that's how relative URLS work, and that's what using <base> does, it changes what relative URLs are based on. If that's not what you want, your link should not be relative, or it should be relative to the base's href (foo#baz)Steakhouse
@JuanMendes where that falls down is if "foo" is an unknown URL.Delp
@RuanMendes I imagine that most people assume href="#xyz" always refers to an anchor on the the current page, not a completely different page. After all the entire point of "#" is to jump to a section of a document, NOT to load another page. The fact it's possible with <base href to make "#" by itself (without any path component) load a completely different page when it doesn't permit that behavior in any other circumstance I would argue not only a technical deficiency, but even a security flaw.Oast
K
33

I found a solution on this site: using-base-href-with-anchors that doesn't require jQuery, and here is a working snippet:

<base href="https://example.com/">

<a href="/test">/test</a>
<a href="javascript:;" onclick="document.location.hash='test';">Anchor</a>

Or without inline JavaScript, something like this:

document.addEventListener('DOMContentLoaded', function(){
  var es = document.getElementsByTagName('a')
  for(var i=0; i<es.length; i++){
    es[i].addEventListener('click', function(e) {
      e.preventDefault()
      document.location.hash = e.target.getAttribute('href')
    })
  }
})
Kuster answered 13/1, 2016 at 11:22 Comment(8)
why is it bad practice?Kuster
There are many reason of why is not good, some of that are explained hereFlores
It is perfectly valid to use inline javascript -- it exists for a reason. The arguments against it in that document are spurious. Should you base your entire large project off inline code? Probably not. Can you use inline code with intention and as a solution to an edge-case/gotchya? Absolutely. It is part of the HTML spec for a reason. Pushing a blanket ban on inline JS because of the filesize of the HTML document is cargo cult nonsense. If you put the same code in an external JS file, the client still downloads those bytes.Ordinance
This solution doesn't degrade well. On browsers with Javascript disabled (or html parsers using regex) your link will be broken.Enucleate
Let's be honest... no modern web page degrades well without JavaScript. Same was largely true in 2017.Gerah
@ChrisBaker You really should avoid inline JS since around 2019 and 2020. If you want your site to be guaranteed to be XSS free, you should use CSP headers that prevent inline JS execution. See csper.io/blog/no-more-unsafe-inlineSteakhouse
@davidebubz The solution works perfectly fine, but there is only one limitation if you can resolve i.e. when you click a #something link, it slides the page to that particular element. upon clicking that #something again, will not do anything, and will not lead to the desired element.Chorus
@Chorus That happens whether this workaround is in place or not. If you click on something but the link is already loaded in the URL box, nothing happens.Steakhouse
E
14

Building upon James Tomasino's answer, this one is slightly more efficient, solves a bug with double hashes in the URL and a syntax error.

$(document).ready(function() {
    var pathname = window.location.href.split('#')[0];
    $('a[href^="#"]').each(function() {
        var $this = $(this),
            link = $this.attr('href');
        $this.attr('href', pathname + link);
    });
});
Enucleate answered 20/11, 2013 at 13:9 Comment(0)
A
9

A little bit of jQuery could probably help you with that. Although base href is working as desired, if you want your links beginning with an anchor (#) to be totally relative, you could hijack all links, check the href property for those starting with #, and rebuild them using the current URL.

$(document).ready(function () {
    var pathname = window.location.href;
    $('a').each(function () {
       var link = $(this).attr('href');
       if (link.substr(0,1) == "#") {
           $(this).attr('href', pathname + link);
       }
    });
}
Advertence answered 13/11, 2011 at 15:16 Comment(2)
Whilst this may be fine in some situations, the demographic for the site I'm making are the sort that may have JavaScript disabled (or not available). Since the only fallback for such a case would be breakage, that seems a bit concerning.Foamflower
Your best bet, then, is to code all links with full relative paths from the baseURL, including anchors.Advertence
C
4

You could also provide an absolute URL:

<base href="https://example.com/">
<a href="/test#test">test</a>

Rather than this

<a href="#test">test</a>
Clack answered 28/3, 2018 at 11:38 Comment(0)
W
3

Here's an even shorter, jQuery based version I use in a production environment, and it works well for me.

$().ready(function() {
  $("a[href^='\#']").each(function() {
    this.href = location.href.split("#")[0] + '#' + this.href.substr(this.href.indexOf('#')+1);
  });
});
Wooton answered 18/9, 2014 at 22:43 Comment(5)
Why the downvote? It may not be elegant, but it's basically the same approach as other answers here that are nowhere near as succinct and yet have been voted up.Wooton
Somebody probably down-voted since it didn't work for THEIR situation and knew not how to adapt. I see nothing wrong with this aside from no mention of the (assuming jQuery) library requirement.Frankhouse
@Robert Good point. I updated the post to reflect the jQuery requirement. Thx for the feedback.Wooton
Suggesting use of jQuery when jQuery is not mentioned is shunned upon.Steakhouse
While this first and foremost a learning site, coders should know how to convert jquery into vanilla javascript. Especially since nearly every example on this thread depends on one js library or another. libraries do the same things, just simplify getting from point A to B :P I upvoted his solution since it is a perfectly valid solution IMOFrankhouse
N
3

I'm afraid there is no way to solve this without any server-side or browser-side script. You can try the following plain JavaScript (without jQuery) implementation:

document.addEventListener("click", function(event) {
  var element = event.target;
  if (element.tagName.toLowerCase() == "a" && 
      element.getAttribute("href").indexOf("#") === 0) {
    element.href = location.href + element.getAttribute("href");
  }
});
<base href="https://example.com/">

<a href="/test">/test</a>
<a href="#test">#test</a>

It also works (unlike the other answers) for dynamically generated (i.e. created with JavaScript) a elements.

Nashville answered 13/1, 2016 at 10:49 Comment(2)
@jor It works fine for me. Which browser are you using?Tufthunter
Firefox 47. I had the hashes concatenated. Try also to open a url that already includes a hash and click on an anchor link; this will append it to the existing hash.Lynea
N
2

If you use PHP, you can use following function to generate anchor links:

function generateAnchorLink($anchor) {
  $currentURL = "//{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}";
  $escaped = htmlspecialchars($currentURL, ENT_QUOTES, 'UTF-8');
  return $escaped . '#' . $anchor;
}

Use it in the code like that:

<a href="<?php echo generateAnchorLink("baz"); ?>">baz</a>
Nashville answered 15/1, 2016 at 10:11 Comment(0)
G
1

To prevent multiple #s in a URL:

document.addEventListener("click", function(event) {
  var element = event.target;
  if (element.tagName.toLowerCase() == "a" &&
    element.getAttribute("href").indexOf("#") === 0) {
    my_href = location.href + element.getAttribute("href");
    my_href = my_href.replace(/#+/g, '#');
    element.href = my_href;
  }
});
Gracye answered 8/12, 2016 at 18:15 Comment(0)
D
1

My approach is to search for all links to an anchor, and prefix them with the document URL.

This only requires JavaScript on the initial page load and preserves browser features like opening links in a new tab. It also and doesn't depend on jQuery, etc.

document.addEventListener('DOMContentLoaded', function() {
  // Get the current URL, removing any fragment
  var documentUrl = document.location.href.replace(/#.*$/, '')

  // Iterate through all links
  var linkEls = document.getElementsByTagName('A')
  for (var linkIndex = 0; linkIndex < linkEls.length; linkIndex++) {
    var linkEl = linkEls[linkIndex]

    // Ignore links that don't begin with #
    if (!linkEl.getAttribute('href').match(/^#/)) {
      continue;
    }

    // Convert to an absolute URL
    linkEl.setAttribute('href', documentUrl + linkEl.getAttribute('href'))
  }
})
Delp answered 28/7, 2020 at 3:19 Comment(0)
B
0

You can use some JavaScript code inside the tag that links.

<span onclick="javascript:var mytarget=((document.location.href.indexOf('#')==-1)? document.location.href + '#destination_anchor' : document.location.href);document.location.href=mytarget;return false;" style="display:inline-block;border:1px solid;border-radius:0.3rem"
 >Text of link</span>

How does it work when the user clicks?

  1. First it checks if the anchor (#) is already present in the URL. The condition is tested before the "?" sign. This is to avoid the anchor being added twice in the URL if the user clicks again the same link, since the redirection then wouldn't work.
  2. If there is sharp sign (#) in the existing URL, the anchor is appended to it and the result is saved in the mytarget variable. Else, keep the page URL unchanged.
  3. Lastly, go to the (modified or unchanged) URL stored by the mytarget variable.

Instead of <span>, you can also use <div> or even <a> tags. I would suggest avoiding <a> in order to avoid any unwanted redirection if JavaScript is disabled or not working, and emulate the look of your <a> tag with some CSS styling.

If, despite this, you want to use the <a> tag, don't forget adding return false; at the end of the JavaScript code and set the href attribute like this <a onclick="here the JavaScript code;return false;" href="javascript:return false;">...</a>.

Burrill answered 19/4, 2020 at 15:38 Comment(0)
W
0

Building upon Joram van den Boezem's answer, but in plain javascript:

document.addEventListener('DOMContentLoaded', function(){
    let pathname = location.href.split('#')[0];
    [].forEach.call(document.querySelectorAll("a[href^='#']"), function(a) {
        a.href = pathname + a.getAttribute("href");
    });
});
Winebibber answered 19/7, 2023 at 12:25 Comment(0)
I
-1

From the example given in the question. To achieve the desired behavior, I do not see the need of using a "base" tag at all.

The page is at http://example.com/foo/

The below code will give the desired behaviour:

<a href="/bar/">bar</a> <!-- Links to "http://example.com/bar/" -->
<a href="#baz">baz</a> <!-- Links to "http://example.com/foo/#baz" -->

The trick is to use "/" at the beginning of string href="/bar/".

Illuminometer answered 13/3, 2019 at 21:16 Comment(1)
Some people may need the base tag to build a view library that adapts to use cases, like being run at both root directory and in a subdirectory. Removing the base tag is not a solution.Germ
S
-2

If you're using Angular 2 or later (and just targeting the web), you can do this:

File component.ts

document = document; // Make document available in template

File component.html

<a [href]="document.location.pathname + '#' + anchorName">Click Here</a>
Satin answered 3/8, 2017 at 21:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.