Anchor links are cool. They make it easy to get the link to a specific section in webpage to share with others. Just hover over a heading and get the clickable link with the URL of the heading. This page lists my experiments with adding Anchor links to this blog.
I used AnchorJS in my first attempt to add anchor links. It was very easy to integrate.
Include
anchor.js
script in the html page.
<script src="/js/jquery-3.4.1.min.js"></script>
<script src="js/apply-styles.js"></script>
<!-- For anchor links -->
<script src="js/anchor-4.2.0.min.js"></script>and call anchors.add() in the $(document).ready call back. This was the
location I used for the other style updates.
/* apply-styles.js */
$(document).ready(function() {
anchors.add();
}AnchorJS worked splendidly for my blog (for the audience of 1 😃). However
the anchorJS website includes an explicit warning against calling
anchors.add() in the $(document).ready call back.
https://www.bryanbraun.com/anchorjs/#dont-run-it-too-late
Don’t add anchors on later events, like
$(document).ready()orwindow.onloadas some browsers will attempt to jump to your ID before AnchorJS can add it to the page. For more details, see github issue #69.
This got me thinking. As my blog is static website, I figured I can add the anchors when building the website and remove the runtime component.
This blog is built using Hakyll which uses Pandoc to convert from Markdown to HTML. I implemented a Pandoc filter to add anchors to headers
-- Create an anchor based on the string
-- add anchorjs-link class for CSS manipulations
-- Use "#" as the display for the anchor
-- Append linkId to "#" to create the link target
addAnchor :: String -> Inline
addAnchor linkId = Link ("", ["anchorjs-link"], []) [Str " #"] ("#" ++ linkId, "")
-- Add a anchor link to each html header
-- Leave everything else as is
modHeader :: Block -> Block
-- We are matching against the header pattern from pandoc-types
-- We take the linkId and use it to add anchor links
-- Header Int Attr [Inline]
modHeader (Header n y@(linkId, _, _) xs) = (Header n y (xs ++ [(addAnchor linkId)]))
-- fallback for all others
modHeader x = x
-- Walk the Pandoc AST and apply modHeader on each node
anchorLinks :: Pandoc -> Pandoc
anchorLinks = walk modHeaderThis script also needs CSS changes to hide the anchors by default and reveal them on hover.
/* Make the anchor transparent by default */
.anchorjs-link {
opacity: 0;
text-decoration: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Make the anchor opaque on hover */
*:hover > .anchorjs-link,
.anchorjs-link:focus {
opacity: 1;
}Here are the links to haskell source and css file.
Sometime after I made the anchor links change, I upgraded the version on pandoc and hakyll I was using. This caused the “#” used the in the anchors to also show in the sidebar. The “#” showed up without any additional html element attached to it.
To solve the problem, I considered splitting the hakyll build pipeline into two
Both the pipelines get combined to form the final output.
Unfortunately my haskell/hakyll skills are not upto the mark.
I used a simpler solution where the pandoc filter creates an empty link. The content of the link is populated with a “#” when the mouse hovers over the heading. This keeps the table of contents clean and the anchor links functional.
The pandoc filter adds anchor links with a non-breaking space as content
addAnchor linkId = Link ("", ["anchorjs-link"], []) [Str "\160"] ("#" ++ linkId, "")The “#” is added on hover to the anchor link.
.anchorjs-link::after {
content: "#";
}