Каретка с помощью css

Я хочу поменять вид каретки с "палочки" на "прямоугольник" как это реализовать?


Ответы (2 шт):

Автор решения: Zhihar

caret-shape и caret-color вам в помощь

https://hcdev.ru/css/caret-color/

https://css-tricks.com/almanac/properties/c/caret-shape/

.other {
    width: 250px;
    height: 40px;
    
    font-size: 30px;
    
    caret: red;
    caret-shape: block;
}

но поддерживается это не у всех :)
<input class = 'other'>

→ Ссылка
Автор решения: Михаил Камахин

На данный момент не знаю как это реализовать на CSS. Предлагаю решение на JS

class CaretManager {
  constructor(editorEl) {
    if (!(editorEl instanceof HTMLElement)) {
      throw new Error("Нужен DOM-элемент редактора");
    }

    this.editorEl = editorEl;
    this.caret = document.createElement("div");
    this.caret.classList.add("caret");
    this.editorEl.appendChild(this.caret);

    this._bindEvents();
  }

  _isSelectionInsideEditor(sel) {
    if (!sel || sel.rangeCount === 0) return false;
    if (document.activeElement !== this.editorEl) return false;

    let node = sel.anchorNode;
    while (node) {
      if (node === this.editorEl) return true;
      node = node.parentNode;
    }
    return false;
  }

  _measureCharWidthFallback() {
    const cs = getComputedStyle(this.editorEl);
    const span = document.createElement("span");
    span.textContent = "M";
    span.style.position = "absolute";
    span.style.left = "-9999px";
    span.style.whiteSpace = "pre";
    span.style.font = cs.font || `${cs.fontSize} ${cs.fontFamily}`;
    document.body.appendChild(span);
    const w =
      span.getBoundingClientRect().width || parseFloat(cs.fontSize) * 0.6;
    document.body.removeChild(span);
    return w;
  }

  _getCaretRect() {
    const sel = window.getSelection();
    if (!sel || sel.rangeCount === 0) return null;
    if (!this._isSelectionInsideEditor(sel)) return null;

    const range = sel.getRangeAt(0).cloneRange();
    range.collapse(true);

    let rect = range.getClientRects()[0] || null;
    let marker = null;

    if (!rect) {
      marker = document.createElement("span");
      marker.textContent = "\u200b";
      Object.assign(marker.style, {
        padding: "0",
        margin: "0",
        border: "0",
        opacity: "0",
        pointerEvents: "none"
      });
      range.insertNode(marker);
      rect = marker.getBoundingClientRect();
      marker.remove();
    }

    const cs = getComputedStyle(this.editorEl);
    if (!rect || rect.height <= 0) {
      const lineH =
        parseFloat(cs.lineHeight) || parseFloat(cs.fontSize) * 1.2;
      rect = rect || {
        left: 0,
        top: 0,
        width: 0,
        height: lineH
      };
      rect.height = lineH;
    }

    if (!rect.width || rect.width < 1) {
      let measured = null;
      try {
        const anchor = sel.anchorNode;
        const off = sel.anchorOffset;
        if (
          anchor &&
          anchor.nodeType === Node.TEXT_NODE &&
          off < anchor.textContent.length
        ) {
          const r2 = document.createRange();
          r2.setStart(anchor, off);
          r2.setEnd(anchor, off + 1);
          const r2rect = r2.getBoundingClientRect();
          if (r2rect && r2rect.width) measured = r2rect.width;
        }
      } catch (e) {}
      rect.width = measured || this._measureCharWidthFallback();
    }

    return rect;
  }

  _getCaretPosition(rect) {
    // Начальные координаты относительно окна
    let x = rect.left + window.scrollX;
    let y = rect.top + window.scrollY;

    // Добавляем scroll всех родительских элементов
    let el = this.editorEl;
    while (el && el !== document.body) {
      x -= el.scrollLeft || 0;
      y -= el.scrollTop || 0;
      el = el.offsetParent;
    }

    return {
      x,
      y
    };
  }

  _updateCaret() {
    const rect = this._getCaretRect();
    if (!rect) {
      this._hideCaret();
      return;
    }

    const {
      x,
      y
    } = this._getCaretPosition(rect);

    this.caret.classList.add("caret_active");
    this.caret.style.width = Math.max(1, Math.round(rect.width)) + "px";
    this.caret.style.height = Math.round(rect.height) + "px";
    this.caret.style.transform = `translate3d(${x}px, ${y}px, 0)`;
    this.caret.style.background =
      getComputedStyle(this.editorEl).color || "black";
  }

  _hideCaret() {
    this.caret.classList.remove("caret_active");
  }

  _bindEvents() {
    const refresh = () => requestAnimationFrame(() => this._updateCaret());

    document.addEventListener("selectionchange", () => {
      const sel = window.getSelection();
      if (this._isSelectionInsideEditor(sel)) refresh();
      else this._hideCaret();
    });

    this.editorEl.addEventListener("input", refresh);
    this.editorEl.addEventListener("keyup", refresh);
    this.editorEl.addEventListener("focus", refresh);
    this.editorEl.addEventListener("blur", () => this._hideCaret());
    this.editorEl.addEventListener("compositionend", refresh);
    this.editorEl.addEventListener("pointerup", refresh);
    this.editorEl.addEventListener("pointercancel", () => this._hideCaret());
    window.addEventListener("resize", refresh);
  }
}

const editor = document.getElementById("editor");
const caretMgr = new CaretManager(editor);
html {
  font-size: 20px;
  font-family: sans-serif;
}

body {
  padding: 20px;
}

.editor {
  width: 90%;
  min-height: 160px;
  padding: 12px;
  border: 1px solid #cfcfcf;
  border-radius: 8px;
  outline: none;
  caret-color: transparent;
  /* прячем нативную каретку */
  white-space: pre-wrap;
}

.caret {
  position: absolute;
  left: 0;
  top: 0;
  width: 10px;
  height: 1em;
  display: block;
  opacity: 0;
  visibility: hidden;
  background: currentColor;
  pointer-events: none;
  z-index: 2147483647;
  border-radius: 2px;
  animation: blink 1s steps(1, end) infinite;
}

.caret_active {
  opacity: 1;
  visibility: visible;
}

@keyframes blink {
  50% {
    opacity: 0;
  }
}
<div id="editor" class="editor" contenteditable="true">
  Нажми или коснись — должна появиться прямоугольная каретка.
</div>

→ Ссылка