Положение каретки и высота выделения для встроенных элементов в Chrome
Я собираюсь создать редактор с Slate или ProseMirror и вижу какое-то странное поведение с Chrome вокруг положения курсора и области выбора при использовании встроенного элемента. Выпуск 1 показан на первом рисунке ниже. Когда положение текстового курсора находится за буквой "f", курсор отображается вверху изображения. Проблема 2 находится на втором изображении - выделение текста показывает выделенную область, высота которой равна высоте встроенного элемента. Есть ли способ контролировать это поведение и вместо этого отображать курсор в позиции текста и выделять только пространство вокруг текста (даже если встроенное изображение увеличивает высоту строки)
Я хотел бы имитировать поведение здесь, из firefox:
Примеры изображений были созданы с использованием демонстрации ProseMirror здесь: https://prosemirror.net/examples/basic/
Минимальный пример (спасибо @Kaiido) с JSBin:
<div contenteditable>Test text<img src="https://upload.wikimedia.org/wikipedia/en/9/9b/Yoda_Empire_Strikes_Back.png">Testing</div>
Не знаю, как это работает в других операционных системах, но я использую macOS Catalina.
3 ответа
Вы можете решить проблему выбора, используя flexbox. Я изначально пробовал использоватьalign-items: flex-end
но у меня было странное поведение клавиш со стрелками. align-items: baseline
похоже, работает на данный момент.
Проблема с изображением очень странная. Я заметил, что Chrome переходит через элементы SVG иначе, чем элементы IMG. На данный момент последняя версия Chrome "ждет" перед пропуском IMG, но позволяет пользователю пропускать SVG, как любой другой символ (стрелка влево пропускает символ, ближайший к svg). Это может быть вызвано базовыми стилями по умолчанию, но я не знаю.
Я собирался опубликовать свои выводы, но понял, что Firefox работает иначе. Firefox имеет очень странное поведение клавиш со стрелками при использовании SVG и / или Flexbox. Курсор перемещается над изображением, но только при нажатии клавиши со стрелкой вправо.
Однако Firefox отлично работает с обычным элементом IMG.
Короче говоря, вам придется визуализировать различные элементы изображения в зависимости от браузера.
// chrome contenteditable ads spaces between spans.
// firefox does not, so you have to add them manually.
if (navigator.userAgent.indexOf("Firefox") > 0) {
Array.from(document.querySelectorAll('#editable span')).forEach(el => {
el.innerHTML += " "
})
}
.flexit {
display: flex;
align-items: baseline;
flex-wrap: wrap;
}
span {
display: inline-block;
white-space: pre;
}
.img-wrapper, .img-wrapper + span {
display: inline;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<h1>chrome</h1>
<div contenteditable="true" class="flexit">
<span>test</span><svg width="200" height="200" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<image href="https://mdn.mozillademos.org/files/6457/mdn_logo_only_color.png" height="200" width="200"/>
</svg><span>Here</span><span>is</span><span>a</span><span>longer</span><span>sentence.</span><span>Notice</span><span>I</span><span>wrapped</span><span>each</span><span>word</span><span>in</span><span>a</span><span>span.</span><span>This</span><span>makes</span><span>the</span><span>text</span><span>appear</span><span>like</span><span>it</span><span>is</span><span>inline.</span>
</div>
<h1>firefox</h1>
<div contenteditable="true" id="editable">
<span>test</span><span class="img-wrapper"><img src="https://mdn.mozillademos.org/files/6457/mdn_logo_only_color.png" height="200" width="200" /></span><span>test</span><span>Here</span><span>is</span><span>a</span><span>longer</span><span>sentence.</span><span>Notice</span><span>I</span><span>wrapped</span><span>each</span><span>word</span><span>in</span><span>a</span><span>span.</span><span>This</span><span>makes</span><span>the</span><span>text</span><span>appear</span><span>like</span><span>it</span><span>is</span><span>inline.</span>
</div>
</body>
</html>
PS Многие редакторы я использовал по умолчанию для полноразмерных изображений.
Изменить: я только что понял, что, похоже, мое решение Firefox также работает в последней версии Chrome. Я думаю, что это работает, потому что я заключил текстовые узлы в элементы SPAN. Элемент SVG по-прежнему работает в Chrome по-другому, но перенос текста в SPAN, похоже, решает большинство проблем.
Во-первых, не манипулируйте ProseMirror DOM, как показано в примере JQuery. На самом деле вы, скорее всего, столкнетесь с проблемами DOM или контентом. ProseMirror использует собственный узел DOM и схему разметки. Если вы хотите манипулировать ProseMirror DOM или добавить плагин, взгляните на Markup, Plugin & Node Apis. Я приложил простой пример кода разметки выравнивания текста. Боковое примечание: причина, по которой Grammarly и другие не имеют плагинов ProseMirror, связана с подходом / моделями DOM. Я должен добавить, что ProseMirror очень хорошо, но, честно говоря, это более продвинутое решение для разработчиков.
Кроме того, хорошие новости заключаются в том, что у вас проблемы с чисто CSS. ProseMirror удаляет все классы и сбрасывает DOM / CSS, поэтому, если вы импортируете документ или скопируете и вставите все / большинство ваших классов, ваши классы исчезнут.
Самый простой подход к решению - обернуть редактор в div и назначить класс для этого div, а затем добавить стили и дочерние стили к этому классу. Причина в том, что селекторы css, такие как img, p, h и т.д., будут применяться только к тегам внутри класса редактора. Без этого вы получите очевидные конфликты CSS.
CSS
- Не используйте flexbox для встроенных изображений, поскольку flexbox не является сеточной системой. На самом деле, если я помню, вы не можете встроить прямые дочерние элементы гибкого контейнера.
- inline в тегах p и img не будет переносить текст, и вы столкнетесь с проблемами, перечисленными выше.
- если вы хотите по-настоящему обернуть и удалить проблему с курсором, которая у вас есть, вам нужно использовать поплавки, например
float: left;
(рекомендуемый подход) - добавить маленькие или большие отступы и границы, чтобы помочь с сворачиванием краев, а также помогает с разделением изображения и текста
- проблема с курсором, с которой вы столкнулись, заключается в том, что, когда вы внутри блока изображения, он вертикально выровнен по верхнему краю, что вы можете исправить с помощью
vertical-align: baseline;
но без поплавков у вас все равно будет курсор, который соответствует высоте изображения, а не текста. Кроме того, если вы не используете поплавки, курсор будет вытянутым, поскольку высота строки фактически равна высоте изображения. Синий цвет - это просто селектор, который вы также меняете с помощью CSS.
<html>
<div class="editor">
<!-- <editor></editor>-->
</div>
</html>
<style>
.editor {
position: relative;
display: block;
padding: 10px;
}
.editor p {
font-weight: 400;
font-size: 1rem;
font-family: Roboto, Arial, serif;
color: #777777;
display: inline;
vertical-align: baseline;
}
.editor img {
width: 50px;
float: left;
padding: 20px;
}
</style>
Пример расширения узла
Пример расширения узла для выравнивания текста, которое можно добавить как панель инструментов. Гораздо более длинный пост, но даже если вы создали узел / плагин для изображений, вам придется иметь дело с тем, как он отображается, например, base64 по сравнению с URL-адресом и т.д., что, кстати, имеет смысл в отношении того, почему они это сделали, но просто добавьте сложность для разработчиков, ищущих SEO и т. д.
export default class Paragraph extends Node {
get name() {
return 'paragraph';
}
get defaultOptions() {
return {
textAlign: ['left', 'center', 'right'],
}
}
inputRules({ type }) {
return [
markInputRule(/(?:\*\*|__)([^*_]+)(?:\*\*|__)$/, type),
]
}
get schema() {
return {
attrs: {
textAlign: {
default: 'left'
}
},
content: 'inline*',
group: 'block',
draggable: false,
inclusive: false,
defining : true,
parseDOM: [
{
tag: 'p',
style: 'text-align',
getAttrs: value => value
}
],
toDOM: (node) => [ 'p', {
style: 'text-align:' + node.attrs.textAlign,
class: `type--base type--std text-` + node.attrs.textAlign
}, 0 ]
};
}
commands ({ type }) {
return (attrs) => updateMark(type, attrs)
}
}
Я попытался немного взломать HTML и CSS.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<div contenteditable><p class="new1" contenteditable>Hello</p><div contenteditable>
<img src="https://upload.wikimedia.org/wikipedia/en/9/9b/Yoda_Empire_Strikes_Back.png"></div>
<p class="new2">Testing</p>
</div>
</body>
</html>
// css
.new2{
font-size:30px;
margin-top:-35px;
margin-left:252px;
padding-left:79px;
}
img{
margin-left:75px;
padding-left:5px;
padding-right:5px;
margin-top:-300px;
}
.new1{
text-overflow:hidden;
padding-top:260px;
margin-bottom:-20px;
font-size:30px;
padding-right:10px;
}
Вот jsfiddle https://jsfiddle.net/wtekxavm/1/