如何为AEM文本组件RTE中的锚链接添加'link-anchor' CSS类
Great question! Fixing that fixed header anchor offset is a common pain point, and adding a custom class is the perfect approach to target those links for CSS adjustments. Here are three reliable methods to implement this in AEM:
Method 1: Automatic Client-Side Injection (Quickest Fix)
If you want an immediate solution without modifying AEM backend configurations, add a small JavaScript snippet to your site’s frontend. This will automatically target all anchor links (those with href starting with #) in your RTE content and apply the class:
// Run after the page and RTE content fully loads document.addEventListener('DOMContentLoaded', function() { // Replace .rte-content with your actual RTE container class if needed const anchorLinks = document.querySelectorAll('.rte-content a[href^="#"]'); anchorLinks.forEach(link => { link.classList.add('link-anchor'); }); });
This works seamlessly and requires no author training—ideal for fast fixes.
Method 2: Configure RTE to Let Editors Apply the Class (Controlled Approach)
If you want content authors to explicitly choose when to apply the anchor class (for more control), extend the RTE’s link dialog to include a class selector. Update your existing RTE configuration as follows:
Step 1: Add a Class Field to the Link Dialog
Modify the <links> node in your RTE config to add a dropdown for predefined classes:
<links jcr:primaryType="nt:unstructured" features="[modifylink,unlink,anchor]"> <dialog jcr:primaryType="nt:unstructured" height="300" width="600"> <items jcr:primaryType="nt:unstructured"> <link jcr:primaryType="nt:unstructured" sling:resourceType="cq/gui/components/authoring/dialogs/richtext/link"/> <!-- Add dropdown for link classes --> <class jcr:primaryType="nt:unstructured" sling:resourceType="cq/gui/components/authoring/dialogs/selection" fieldLabel="Link Style" name="./class" type="select"> <options jcr:primaryType="nt:unstructured"> <anchor jcr:primaryType="nt:unstructured" text="Anchor Link (Fixed Header)" value="link-anchor"/> <normal jcr:primaryType="nt:unstructured" text="Normal Link" value=""/> </options> </class> </items> </dialog> </links>
Step 2: Train Authors to Use the Selector
Now when authors create or edit an anchor link in the RTE, they can select "Anchor Link (Fixed Header)" from the dropdown to apply the link-anchor class automatically.
Method 3: Server-Side Sling Filter (Fully Automatic & Consistent)
For a hands-off solution that adds the class to all anchor links at render time (no author action required), create a Sling Filter. This ensures every anchor link gets the class, regardless of how it was created.
Example Filter Code
import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.SlingHttpServletResponse; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ValueMap; import org.apache.sling.engine.EngineConstants; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Property; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; import com.day.cq.wcm.foundation.richtext.RichTextException; import com.day.cq.wcm.foundation.richtext.RichTextParser; @Component(service = javax.servlet.Filter.class, property = { EngineConstants.SLING_FILTER_SCOPE + "=" + EngineConstants.FILTER_SCOPE_REQUEST, EngineConstants.SLING_FILTER_PATTERN + "=/content/.*.html", EngineConstants.SLING_FILTER_ORDER + ":Integer=1000" }) public class AnchorLinkClassFilter implements javax.servlet.Filter { @Override public void doFilter(javax.servlet.ServletRequest request, javax.servlet.ServletResponse response, javax.servlet.FilterChain chain) throws java.io.IOException, javax.servlet.ServletException { SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) request; Resource resource = slingRequest.getResource(); ValueMap properties = resource.getValueMap(); String rteContent = properties.get("text", String.class); if (rteContent != null && !rteContent.isEmpty()) { try { RichTextParser parser = new RichTextParser(rteContent); parser.parse(new ContentHandler() { private ContentHandler delegate; @Override public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { if ("a".equals(localName)) { String href = atts.getValue("href"); if (href != null && href.startsWith("#")) { AttributesImpl newAtts = new AttributesImpl(atts); String existingClass = atts.getValue("class"); String newClass = existingClass != null ? existingClass + " link-anchor" : "link-anchor"; newAtts.setAttribute(newAtts.getIndex("class"), "", "class", "CDATA", newClass); delegate.startElement(uri, localName, qName, newAtts); return; } } delegate.startElement(uri, localName, qName, atts); } // Implement remaining ContentHandler methods by delegating to the original handler @Override public void setDocumentLocator(org.xml.sax.Locator locator) { delegate.setDocumentLocator(locator); } @Override public void startDocument() throws SAXException { delegate.startDocument(); } @Override public void endDocument() throws SAXException { delegate.endDocument(); } @Override public void startPrefixMapping(String prefix, String uri) throws SAXException { delegate.startPrefixMapping(prefix, uri); } @Override public void endPrefixMapping(String prefix) throws SAXException { delegate.endPrefixMapping(prefix); } @Override public void endElement(String uri, String localName, String qName) throws SAXException { delegate.endElement(uri, localName, qName); } @Override public void characters(char[] ch, int start, int length) throws SAXException { delegate.characters(ch, start, length); } @Override public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { delegate.ignorableWhitespace(ch, start, length); } @Override public void processingInstruction(String target, String data) throws SAXException { delegate.processingInstruction(target, data); } @Override public void skippedEntity(String name) throws SAXException { delegate.skippedEntity(name); } public ContentHandler init(ContentHandler delegate) { this.delegate = delegate; return this; } }.init(parser.getContentHandler())); // Update the response with modified content (use a response wrapper for production) resource.adaptTo(org.apache.sling.api.resource.ModifiableValueMap.class).put("text", parser.getHTML()); } catch (RichTextException e) { e.printStackTrace(); } } chain.doFilter(request, response); } @Override public void init(javax.servlet.FilterConfig filterConfig) throws javax.servlet.ServletException {} @Override public void destroy() {} }
Adjust the filter pattern (/content/.*.html) and RTE property name (text) to match your site’s structure.
Final CSS Fix for Offset
Once you’ve added the class, apply this CSS to fix the fixed header offset:
/* Adjust the value to match your header's height */ .link-anchor { scroll-margin-top: 80px; }
For older browser support, you can use the padding-top trick instead:
.link-anchor { display: block; padding-top: 80px; margin-top: -80px; }
内容的提问来源于stack exchange,提问作者JudyP

