On Monday, I shared a new interactive Code Sandbox I had built using vanilla JavaScript, and explained how I live render code into an iframe.
Today, I wanted to share how I syntax highlight code as the user types in real time.
Let's dig in!
The one simple trick that makes this work!
I tried a bunch of things to get this to work, but the trick that made it happen ended up being stupidly simple.
- I added a
pre
element (with nestedcode
element) in the UI before my eachtextarea
. - I position the
textarea
directly on top of thepre
element using CSS Grid. - I set the text color and background on my
textarea
totransparent
, so you can see thepre
element behind it. - I otherwise style them to look the same: same typeface, font size, padding, etc.
- When the user types, I mirror their text into the
pre > code
element in realtime, and syntax highlight that.
The result: when users type, their text gets mirrored and highlighted into the pre
element, but because the actual text they type is transparent, it looks like the text they type is what's being highlighted.
Let's look at how that actual works.
Adding pre
and code
elements
I updated the UI to include nested pre
and code
elements. Then I wrapped both those and the textarea
in a div
with the .editor
class.
<div class="editor"> <pre class="lang-html"><code></code></pre> <textarea id="html" spellcheck="false" autocorrect="off" autocapitalize="off" translate="no"></textarea> </div>
Next, I added some CSS to turn the .editor
into a grid
layout.
I positioned the pre
and textarea
elements in the example same spot. Because the textarea
is second in the layout, it sits on top.
.editor { display: grid; grid-template-columns: 1fr; grid-template-rows: 1fr; gap: 0; } .editor pre, .editor textarea { grid-area: 1 / 1 / 2 / 2; }
Styling the textarea
, pre
, and code
elements
This part is largely a matter of taste. I added CSS to make my pre
and code
elements look the way I want, and then added .editor textarea
to those code declarations so that it would look the same.
pre, .editor textarea { background-color: #f7f7f7; color: #272727; display: block; line-height: 1.5; margin: 0 0 1.5625em; overflow: auto; padding: 0.8125em; white-space: pre-wrap; word-break: break-all; } code { word-wrap: break-word; } pre, code, .editor textarea { font-family: Menlo, Monaco, "Courier New", monospace; font-size: 0.875em; } pre code { color: inherit; font-size: 1em; }
I added one additional style block just for the .editor textarea
element.
This sets the background-color
and color
to transparent
, and removes some of the default textarea
styling. It also adds a caret-color
property. This makes the caret visible as the user types, even though the text itself is invisible.
.editor textarea { background-color: transparent; border: none; color: transparent; caret-color: #000; overflow: hidden; resize: none; width: 100%; }
Mirroring the text
Inside the inputHandler()
function, before setting up my debounce
function to render the iframe, I mirror the textarea
text into the pre > code
element.
Because I have tight control over the layout on this, I can use the Element.previousElementSibling
element to get the pre
element, and the firstChild
property of that to get the nested code
element.
I use the Node.textContent
property to set its text to whatever the value
of the textarea
(or event.target
) is.
/** * Handle input events on our fields * @param {Event} event The event object */ function inputHandler (event) { // Only run on our three fields if ( event.target !== html && event.target !== css && event.target !== js ) return; // Clone text into pre immediately let code = event.target.previousElementSibling.firstChild; if (!code) return; code.textContent = event.target.value; // ... }
Syntax highlighting the code
This is actually the most code-heavy part of the whole library, because it requires a fair bit of JS to do properly.
I used the fantastic Prism.js library from Lea Verou. After you select your desired theme, download the development version (at least of the CSS).
By default, Prism.js will highlight every pre
element with a .lang-
or .language-
class on the page, but we want to control when and where it runs. To do that, we can set it to manual mode.
You can do that by either adding a [manual]
attribute to the script
element when you load it, or setting it with some JS configurations. Here, we'll use the attribute.
<script src="prism.js" manual></script>
In the Prism.js CSS file, we can remove all of the default pre
and code
styling. We only need the syntax related stuff: the items prefixed with .token
in the CSS file.
We'll load that on our page as well.
<link rel="stylesheet" type="text/css" href="prism.css">
Back in our inputHandler()
function, we'll run the Prism.highlightElement()
method to highlight the code in the element we just mirrored our text into.
// Clone text into pre immediately let code = event.target.previousElementSibling.firstChild; if (!code) return; code.textContent = event.target.value; // Highlight the syntax Prism.highlightElement(code);
Now, as the user types, their code is mirrored and highlighted.
Try it out!
You can download the code from this tutorial on GitHub.
For me, the next step after this was to create a Web Component: code-sandbox
. This lets me easily setup multiple sandboxes on a page, only include the code types I need, and write just the minimal amount of HTML required.
The Web Component handles rendering the pre
and code
elements, injecting the required styles, wiring up event listeners, and more. But… that's a much bigger tutorial.
I have a favor to ask. If you enjoyed this email, could you forward it to someone else who you think might also like it, or share a link to my newsletter on your favorite social media site?
Cheers,
Chris
Want to share this with others or read it later? View it in a browser.
0 Komentar untuk "[Go Make Things] How to add syntax highlighting to code as a user types in realtime with vanilla JavaScript"