so that they can scroll together and remain lined up.
The CSS
Here is the entire CSS that I used for the editor.
html,
body {
height: 100vh;
width: 100vw;
padding: 0;
margin: 0;
}
#editor {
height: 100%;
width: 100%;
box-sizing: border-box;
display: grid;
grid-template: 1fr / 1fr;
place-items: top;
overflow: auto;
padding: 2rem;
background-color: #1a1723;
}
#editor .code,
#editor .preview {
all: unset;
}
#editor .code,
#editor .preview {
height: auto;
grid-column: 1 / 1;
grid-row: 1 / 1;
font-family: Monaco, monospace;
font-size: 16px;
resize: none;
line-height: 24px;
white-space: pre-wrap;
overflow: hidden;
}
#editor .preview {
pointer-events: none;
color: #ccc;
}
#editor .code {
color: rgba(255, 255, 255, 0);
caret-color: magenta;
}
Overlapping the elements
One of the key parts here is using display grid to overlay the textarea and the preview
#editor {
...
display: grid;
grid-template: 1fr / 1fr;
place-items: top;
overflow: auto;
...
}
I am using the fr unit to overlap the two elements inside the container. You can read more about fr at CSSTricks here.
Stying the textarea and preview identically
You may notice that I apply the same rules to the preview and the textarea. This ensures that they overlap precisely.
#editor .code,
#editor .preview {
height: auto;
grid-column: 1 / 1;
grid-row: 1 / 1;
font-family: Monaco, monospace;
font-size: 16px;
resize: none;
line-height: 24px;
white-space: pre-wrap;
overflow: hidden;
}
Make the textarea transparent
Using rgba for the text color allows me to have the text entirely transparent, so it seems like you are selecting and editing the preview.
#editor .code {
color: rgba(255, 255, 255, 0);
caret-color: magenta;
}
A small bit of Javascript
We do need a small amount of Javascript to tie this all together. Let's take a look at what we need.
const $preview = document.querySelector("#editor .preview");
const $code = document.querySelector("#editor .code");
function mirror() {
// make textarea grow to height of content so we can scroll together
$code.style.height = $code.scrollHeight;
// update the preview underlay with the syntax highlight
$preview.innerHTML = Prism.highlight(
$code.value,
Prism.languages.javascript,
"javascript",
);
}
// insert two spaces on tab
$code.addEventListener("keydown", (ev) => {
if (ev.code === "Tab") {
ev.preventDefault();
$code.setRangeText(" ", $code.selectionStart, $code.selectionStart, "end");
mirror();
}
});
$code.addEventListener("input", mirror);
mirror();
We will be using Prism for this, but you could use anything you like.
The mirror() function is doing a couple of things here.
<div id="editor">
<div class="preview"></div>
<textarea class="code" spellcheck="false"></textarea>
</div>
This ensures that as you type, the height of the
html,
body {
height: 100vh;
width: 100vw;
padding: 0;
margin: 0;
}
#editor {
height: 100%;
width: 100%;
box-sizing: border-box;
display: grid;
grid-template: 1fr / 1fr;
place-items: top;
overflow: auto;
padding: 2rem;
background-color: #1a1723;
}
#editor .code,
#editor .preview {
all: unset;
}
#editor .code,
#editor .preview {
height: auto;
grid-column: 1 / 1;
grid-row: 1 / 1;
font-family: Monaco, monospace;
font-size: 16px;
resize: none;
line-height: 24px;
white-space: pre-wrap;
overflow: hidden;
}
#editor .preview {
pointer-events: none;
color: #ccc;
}
#editor .code {
color: rgba(255, 255, 255, 0);
caret-color: magenta;
}
Then we are using Prism to take the code from the