Разместите SVG <text> в том же положении, что и HTML <div> с текстом внутри

Я пытаюсь разместить svg с текстом точно в том же положении, что и div с текстом, независимо от используемого семейства шрифтов или размера шрифта.

Проблема связана с тем, что первый элемент смещен по позициям x и y.

Я пытаюсь установить оба элемента в абсолютное положение сверху 0 слева 0, но они не совпадают.

Конечно, вручную добавление правильного смещения x и y работает, но мне нужно общее решение.

Вот пример, если запустить, то тексты не будут правильно располагаться один поверх другого. Я пытаюсь заставить это работать с любым шрифтом и любым размером.

<div style = "position:absolute;color:red;font-family:Verdana;font-size:20px; margin-left:0; padding-left:0; left:0; ">
  First Line
</div>
<div style = "position:absolute;top:0;left :0; background:transparent;"> 
<svg width = "100" height = "20" xmlns = "http://www.w3.org/2000/svg">
<text x = "0" y = "0" font-family = "Verdana" text-anchor = "start" dominant-baseline = "hanging" font-size = "20" fill = "black">First Line</text>
</svg>
</div>

Вот еще код https://codepen.io/Cristian-M/pen/VwJzXJv

Измените размер шрифта (увеличьте его), и смещение будет видно все больше и больше.

Есть идеи? Спасибо!

позиция: абсолютная, т. е. вам не хватает : в случае div.

Robert Longson 08.08.2024 20:10

Для SVG мне кажется, что работает y = "1.4em", а не доминирующая базовая линия.

Robert Longson 08.08.2024 20:12

Исправлено недостающее : . Использование 1.4em работает, но если вы измените шрифт или размер текста, это больше не будет работать. Я пытаюсь найти универсальное решение, независимо от семейства и размера шрифта.

BlasterGod 08.08.2024 20:44

Ну, единицы em различаются в зависимости от размера шрифта, так что вроде бы все в порядке. С какими шрифтами не работает?

Robert Longson 08.08.2024 21:13

Я пробовал использовать em, но при изменении размера шрифта все равно не работает. Проверьте это codepen.io/Cristian-M/pen/VwJzXJv увеличьте размер шрифта, и смещение станет все более заметным. Я пробовал использовать em вместо px, но та же проблема.

BlasterGod 08.08.2024 21:32
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
2
5
65
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

@blaster god, я думаю, тебе нужно будет поместить оба элемента в обертку. Установите обертку в относительное позиционирование. Затем установите для обоих элементов абсолютное позиционирование, а верхний и левый установите на 0. Кроме того, убедитесь, что высота строки элемента div равна размеру его шрифта. Используйте один и тот же размер шрифта и семейство шрифтов для обоих элементов и удалите лишние элементы.

First Line First Line

Это не работает. Попробуйте увеличить размер шрифта до 50, и смещение станет все более и более очевидным. <div style = "position:relative"> <div style = "position:absolute;color:red;font-family:Verdana;font-‌​size:50px; поля слева:0; отступа слева:0; слева :0; line-height:50px;"> Первая строка </div> <div style = "position:absolute;top:0;left :0; background:transparent;"> <svg width = "300" height = " 50" xmlns = "w3.org/2000/svg"> <text x = "0" y = "0" font-family = "Verdana" text-anchor = "start" доминант-baseline = "висит" font-size = "50" fill = "black">Первая строка</text> </svg> </div> </div>

BlasterGod 08.08.2024 20:52

Боюсь, вы сможете решить эту задачу лишь частично. В основном из-за ограничений SVG по компоновке текста и макету, которые делают идеальную (адаптированную) репликацию HTML-элементов совершенно невозможной:

  • нет переноса строк в SVG (надеюсь, мне придется пересмотреть это, когда, например, inline-size будет доработано в спецификациях W3C и принято – я не буду задерживать дыхание...)
  • нет автоматической настройки ограничивающей рамки на основе контента (не говоря уже о функциях макета CSS, таких как flex или grid)

Другими словами: чтобы эмулировать/реплицировать макеты HTML в SVG, нам нужно вычислить подходящие значения с помощью JavaScript.

Подход 1: внедрить SVG как встроенные элементы.

По сути, мы наследуем большинство свойств шрифта HTML от элемента SVG <text>, поскольку мы скорее внедряем «двойник» SVG в существующий контекст макета HTML/CSS. В частности, мы устанавливаем базовую линию текста SVG в нижней части viewBox и применяем overflow:visible к обрезанным нижним или верхним элементам: элемент SVG будет перемещаться как текстовый элемент HTML с учетом унаследованного размера шрифта.

let svg = document.querySelector('.svgText')
let {width} = svg.getBBox();

// adjust viewBox
svg.setAttribute('viewBox', [0,0, width, 1].join(' '));


// fontSize change
inputFontSize.addEventListener('input', e=>{
  let fontSize = +e.currentTarget.value;
  document.body.style.fontSize = `${fontSize}px`;
})
body {
  font-family: verdana;
  font-size: 80px;
}

* {
  box-sizing: border-box;
}

.fnt-siz, .tools,
h3 {
  font-size: 16px;
  line-height: 1.2em;
}

.tools {
  position: sticky;
  top: 0;
  background: #000;
  color: #fff;
  padding: 0.5em;
  font-size: 20px;
  line-height: 1.2em;
}

.htmlText {
  position: absolute;
  background: yellow;
  color: red;
  margin-left: 0;
  padding-left: 0;
  left: 0.5em;
  top: 2em;
}

svg {
  font-size: 1px;
  letter-spacing: inherit;
}

.svgTextOverlay {
  position: relative;
}

svg {
  font-size: 1em;
  height: 1em;
  overflow: visible;
  outline: 2px dotted red;
}

.svgText {
  position: absolute;
  left: 0;
  top: 0;
}
<div class = "tools">
  <p><label>fontsize <input id = "inputFontSize" type = "range" value = "80" min = "10" max = "200"></label></p>
</div>

  <h3>Synchonize SVG with HTML font-size</h3>
  <div class = "htmlText">
    <span class = "svgTextOverlay">
      Hamburg fonts
      <!-- svg text overlay -->
      <svg id = "svg1" class = "svgText" viewBox = "0 0 1 1"  >
        <text x = "0" y = "1" font-size = "1" >Hamburg fonts</text>
      </svg>
    </span>
  </div>

Как видите, мы можем воспроизвести свойства HTML-текста довольно точно… если только не получим разрыв строки.

Самый простой заполнитель для замены текста SVG может выглядеть примерно так:

SVG

<svg class = "svgText" viewBox = "0 0 1 1">
  <text y = "1" font-size = "1">
    Your text
  </text>
</svg>

Требуемый CSS для того, чтобы SVG «плавал» по базовой линии, как обычный текст (а также наследовал такие свойства, как цвет текста), будет:

CSS

.svgText{
  display:inline-block;
  font-size: 1em;
  height: 1em;
  overflow: visible;
  fill: currentColor;
}

JS

Требуется для расчета подходящей viewBox ширины.

let {width} = svg.getBBox();
svg.setAttribute('viewBox', [0,0, width, 1].join(' '));

let svgTexts = document.querySelectorAll('.svgText');


(async() => {
  await document.fonts.ready;
  replaceHTMLText(svgTexts)
})();


function replaceHTMLText(svgTexts) {
  svgTexts.forEach(svg => {
    let {
      width
    } = svg.getBBox();
    svg.setAttribute('viewBox', [0, 0, width, 1].join(' '));
  })
}
body {
  font-size: 5vmin;
  font-family: georgia, serif;
  padding: 0.5em;
}

.svgText {
  display: inline-block;
  font-size: 1em;
  height: 1em;
  overflow: visible;
  fill: currentColor;
}

.resize {
  width: 75%;
  overflow: auto;
  padding: 0.2em;
  outline: 1px solid #ccc;
  resize: both;
}

p:hover .svgText {
  color: red
}
<h3>Hover to see the SVG text elements</h3>
<div class = "resize">
  <p>One morning, when Gregor Samsa woke from troubled dreams, he found himself transformed in his bed into a horrible vermin. He lay on his armour-like back, and if he lifted his head a little he could see his brown belly, <svg class = "svgText" viewBox = "0 0 1 1"><text y = "1" font-size = "1">slightly</text></svg>    domed and divided by arches into stiff sections. The bedding was <svg class = "svgText" viewBox = "0 0 1 1"><text y = "1" font-size = "1">hardly</text></svg> able to cover it and seemed ready to slide off any moment.</p>
</div>

Подход 2: вычислить смещение по оси measureText() для абсолютного положения.

Мы можем получить некоторые метрики вертикального шрифта с помощью MeasureText() , чтобы вычислить подходящие смещения для SVG. См. также «4.12.5.1.11 Рисование текста в растровом изображении»

(async() => {
  await document.fonts.ready;
  positionSVGTextOverlay(htmlText, svgText);


})()

function positionSVGTextOverlay(htmlEl, svgEl) {
  // copy styles
  let styles = window.getComputedStyle(htmlEl);
  let {
    fontFamily,
    fontSize
  } = styles;
  fontSize = parseFloat(fontSize);


  //get HTML element position
  let {
    x,
    y,
    bottom,
    width,
    height
  } = htmlEl.getBoundingClientRect();

  // get vertical metrics to calculate y offset
  let metrics = getFontMetrics(fontFamily);
  let {
    fontBoundingBoxAscent,
    fontBoundingBoxDescent,
    hangingBaseline,
    alphabeticBaseline
  } = metrics;

  let renderedHeight = (fontBoundingBoxDescent + alphabeticBaseline);
  let scale = 1 + (fontBoundingBoxDescent + alphabeticBaseline) / fontBoundingBoxDescent
  let top = Math.floor(bottom - fontSize * scale)


  svgEl.setAttribute(
    "style",
    `
  position:absolute;
  display:inline-block;
  font-size: ${fontSize}px;
  font-family: ${fontFamily};
  left: ${x}px;
  top: ${ top  }px;
  `
  );

  //adjust viewBox
  let bb = svgEl.getBBox();
  svgEl.setAttribute('viewBox', [0, 0, bb.width, 1].join(' '));


}



function getFontMetrics(font) {
  const canvas = document.createElement("canvas");
  const ctx = canvas.getContext("2d");
  ctx.font = `1000px ${font}`;
  ctx.textBaseline = "top";
  return ctx.measureText('H');
}
html,
body {
  margin: 0;
  padding: 0;
}

.fnt-siz,
.tools,
h3 {
  font-size: 16px;
  line-height: 1.2em;
}

.tools {
  position: sticky;
  top: 0;
  background: #000;
  color: #fff;
  padding: 0.5em;
  font-size: 20px;
  line-height: 2.5em;
}

.htmlText {
  font-family: sans-serif, Verdana, Georgia;
  position: absolute;
  background: yellow;
  color: red;
  margin-left: 0;
  padding-left: 0;
  left: 50px;
  top: 200px;
  font-size: 90px;
}

.svgText {
  font-size: 1em;
  height: 1em;
  overflow: visible;
  outline: 1px solid red;
}
<div class = "tools">
  <p><label>fontsize <input id = "inputFontSize" type = "range" value = "80" min = "10" max = "200"></label></p>
</div>

<h3>Synchonize SVG with HTML font-size</h3>
<div id = "htmlText" class = "htmlText">
  First Line
</div>

<!-- svg text overlay -->
<svg id = "svgText" class = "svgText" viewBox = "0 0 1 1">
  <text y = "1" font-size = "1" >First Line
</text>
</svg>

<script>
  // fontSize change
  inputFontSize.addEventListener("input", (e) => {
    let fontSize = +e.currentTarget.value;
    htmlText.style.fontSize = `${fontSize}px`;
    positionSVGTextOverlay(htmlText, svgText);

  });
</script>

Однако мы по-прежнему не можем эмулировать разрывы строк.

Спасибо большое за подробный ответ. Мне удалось найти идеальное решение несколько часов назад. Все, что мне нужно было сделать, это установить доминирующую базовую линию по центру (вместо висячей, средней или любой другой) и установить позицию Y на половину размера шрифта. И это один на один с текстом div, независимо от размера или типа шрифта.

BlasterGod 10.08.2024 22:06
Ответ принят как подходящий

Проведя тесты, кажется, я нашел решение. Если вы хотите, чтобы SVG <text был идентичен dom <div text, вам нужно установить:

доминант-базовая линия="центр" (не средний, не висящий или какой-либо другой).

Пример:

<svg  width = "300" height = "300" xmlns = "http://www.w3.org/2000/svg">
<text x = "0" y = "40px" font-family = "verdana" text-anchor = "start" font-size = "80px" dominant-baseline = "central"   fill = "black">First Line</text>
  <text x = "0" y = "120px" font-family = "verdana" text-anchor = "start" font-size = "80px" dominant-baseline = "central"   fill = "black">Second Line</text>
</svg>

И вам нужно установить положение текста Y, чтобы оно было вдвое меньше размера шрифта.

вот ручка кода, показывающая простое решение: https://codepen.io/Cristian-M/pen/VwJzxmK

Другие вопросы по теме