Add a caret to an element in CSS

Viewed 378

problem

I made a <textarea> and used Javascript to put the value into a <pre> tag (contenteditable was being weird), and I want a way to put a blinking caret in the <pre>, like pressing F7. I couldn't just have used the <textarea> because I'm making a JSON formatter (I was bored) and I wanted to add syntax highlighting.

what i have

document.querySelector("pre.stuff").addEventListener("click", function() {
  document.querySelector("textarea.text").focus();
});
document.querySelector("textarea.text").addEventListener("input", function() {
    document.querySelector("pre.stuff").innerText = this.value;
});
textarea.text {
  position: absolute;
    border: none;
  padding: 0px;
  opacity: 0;
  width: 0px;
  height: 0px;
}
pre.stuff {
    width: calc(100% - 20px);
    height: 250px;
    border: solid gray 2px;
    margin: 0px;
    border-radius: 5px;
    padding: 10px;
    transition: 0.3s;
    font-family: "Source Code Pro", monospace;
    tab-size: var(--tab-size);
    overflow: scroll;
    cursor: text;
}
textarea.text:focus ~ pre.stuff, pre.stuff:focus {
    outline: none;
    box-shadow: #0066ff55 0px 0px 0px 4px; /* glowwww */
    border: solid #66a3ff 2px;
}
body {
    margin: 16px;
}
<link href="https://fonts.googleapis.com/css2?family=Source+Code+Pro&display=swap" rel="stylesheet">
<textarea class="text"></textarea>
<pre class="stuff" spellcheck="false" tabindex="-1"></pre>
<br>
I removed the syntax highlighting part :P

Also, the focus has to be on the <textarea> or it won't work.

2 Answers

For this, you would need an absolutely positioned element acting as a caret as there is no property of textarea that I know of which allows you to manipulate the caret. So you would have to create custom events whenever the value of your textarea changes. Here is how you would do so.

Please note that you can remove the transition of the caret if you find it irritating. Also please note: for this i am using a library, found at this link

const txtarea = document.querySelector("textarea.text");
        const invis = $('#faux');


        document.querySelector("pre.stuff").addEventListener("click", function() {
          document.querySelector("textarea.text").focus();
        });
        document.querySelector("textarea.text").addEventListener("input", function() {
            document.querySelector("pre.stuff").innerText = this.value;
        });

        txtarea.addEventListener("keyup", move_caret);
        txtarea.addEventListener("keydown", move_caret);


        function move_caret() {
            let select = document.getSelection();
            let index = txtarea.selectionStart;

            let background = getComputedStyle(txtarea).color;

            const editor = $("textarea.text");
            let pos = editor.caret('position');
            let left = pos.left + 2 + "px";
            let top = pos.top + 2 + "px";
            let height = pos.height + "px";
            var css_data = {
                left,
                top,
                height,
                background
            };
            $("#caret").css(css_data);

        }
* {
            position: relative;
        }
        textarea.text {
            min-width: calc(100% - 20px);
            word-wrap: nowrap;
            height: 250px;
            border: solid gray 2px;
            margin: 0px;
            border-radius: 5px;
            padding: 10px;
            transition: 0.3s;
            font-family: "Source Code Pro", monospace;
            tab-size: var(--tab-size);
            overflow: scroll;
            cursor: text;
            opacity: 0;
            pointer-events: none;
            position: absolute;
        }
        pre.stuff {
            max-width: calc(100% - 20px);
            word-wrap: break-word;
            height: 250px;
            border: solid gray 2px;
            margin: 0px;
            border-radius: 5px;
            padding: 10px;
            transition: 0.3s;
            font-family: "Source Code Pro", monospace;
            tab-size: var(--tab-size);
            overflow: scroll;
            cursor: text;
        }
        textarea.text:focus ~ pre.stuff, pre.stuff:focus {
            outline: none;
            box-shadow: #0066ff55 0px 0px 0px 4px; /* glowwww */
            border: solid #66a3ff 2px;
        }
        body {
            margin: 16px;
        }


        #caret {
            width: 2px; 
            display: inline; 
            position: absolute;

            transition: left 137ms linear, top 137ms linear;
            animation: blink 450ms linear alternate infinite;
        }

        @keyframes blink {
            0% {opacity: 0;}
            20% {opacity: .5;}
            75% {opacity: .78}
            100% {opacity: 1}
        }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/Caret.js/0.3.1/jquery.caret.min.js" integrity="sha512-qclRGh1kwCdmGsi68M9XYAhbCC4xpGRq9VqVlYAQmsG29wQG0DKke/QiMFmuFY1NGXdJ75Wjkhez5nMcuTelgQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<div id="caret"></div>
    <span id="faux" style="display:none"></span>
    <link href="https://fonts.googleapis.com/css2?family=Source+Code+Pro&display=swap" rel="stylesheet">
    <textarea class="text"></textarea>
    <div style="display: inline; width: min-content; height: min-content;">
        <pre class="stuff" spellcheck="false" tabindex="-1"></pre>
        
    </div>
    <br>

i fixed it

I set the width and height of the <textarea> to the <pre>'s width and height, and set color to transparent and caret-color to black. I also removed the border and all the other things, and apparently it works! It's kind of hacky, but when I set the font-size and font-family to the same as the <pre> tag, it works perfectly.

it's here

document.querySelector("pre.stuff").addEventListener("click", function() {
  document.querySelector("textarea.text").focus();
});
document.querySelector("textarea.text").addEventListener("input", function() {
    document.querySelector("pre.stuff").innerText = this.value;
});
textarea.text {
  position: absolute;
    border: solid transparent 2px;
  padding: 10px;
  width: calc(100% - 20px);
  height: 250px;
  color: transparent;
  caret-color: black;
  z-index: -1;
  font-size: 17px;
  outline: none;
  font-family: "Source Code Pro", monospace;
  background-color: transparent;
}
pre.stuff {
    width: calc(100% - 20px);
    height: 250px;
    border: solid gray 2px;
    margin: 0px;
    border-radius: 5px;
    padding: 10px;
    transition: 0.3s;
    font-family: "Source Code Pro", monospace;
    tab-size: var(--tab-size);
    overflow: scroll;
    cursor: text;
  font-size: 17px;
}
textarea.text:focus ~ pre.stuff, pre.stuff:focus {
    outline: none;
    box-shadow: #0066ff55 0px 0px 0px 4px; /* glowwww */
    border: solid #66a3ff 2px;
}
body {
    margin: 16px;
}
<link href="https://fonts.googleapis.com/css2?family=Source+Code+Pro&display=swap" rel="stylesheet">
<textarea class="text"></textarea>
<pre class="stuff" spellcheck="false" tabindex="-1"></pre>
<br>
I removed the syntax highlighting part :P

It's not perfect, but it works for me.

Related