Рамка вокруг обтекающего текста
Можно ли сделать обводку вокруг текста, так чтобы в первом случае она была _|-образной, а во втором случае как обычный прямоугльник? К размерам текста и блоков и расположению не стоит привязываться, выставил просто для наглядности. Завтра картинка может быть и ниже и справа или вообще отсутсвовать - это неважно. Блоков с картинкой в ряду может быть и 2 и 3, большее кол-во не предполагается. В целом нужно чтобы как и текст, рамка тоже обтекала
.img {
height: 100px;
width: 200px;
background-color: red;
float: left;
margin-right: 8px;
}
.text {
margin-bottom: 8px;
}
<div class="container">
<div class="img">IMG</div>
<div class="text">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus expedita, facilis, hic id ipsa ipsam laboriosam libero mollitia nam odio possimus recusandae sed similique tempore unde veniam vitae voluptatem voluptates.Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus expedita, facilis, hic id ipsa ipsam laboriosam libero mollitia nam odio possimus recusandae sed similique tempore unde veniam vitae voluptatem voluptates.Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus expedita, facilis, hic id ipsa ipsam laboriosam libero mollitia nam odio possimus recusandae sed similique tempore unde veniam vitae voluptatem voluptates.Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus expedita, facilis, hic id ipsa ipsam laboriosam libero mollitia nam odio possimus recusandae sed similique tempore unde veniam vitae voluptatem voluptates.
</div>
<div class="text">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus expedita, facilis, hic id ipsa ipsam laboriosam libero mollitia nam odio possimus recusandae sed similique tempore unde veniam vitae voluptatem voluptates.Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus expedita, facilis, hic id ipsa ipsam laboriosam libero mollitia nam odio possimus recusandae sed similique tempore unde veniam vitae voluptatem voluptates.Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus expedita, facilis, hic id ipsa ipsam laboriosam libero mollitia nam odio possimus recusandae sed similique tempore unde veniam vitae voluptatem voluptates.Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus expedita, facilis, hic id ipsa ipsam laboriosam libero mollitia nam odio possimus recusandae sed similique tempore unde veniam vitae voluptatem voluptates.
</div>
</div>
Примерно так должно выглядеть:

Очень желательно, чтобы решение было на чистом CSS. Если надо структруру HTML можете менять на своё усмотрение, главное отображение и что картинка - это отдельный от текста блок
UPD
В поисках решения наткнулся на такой интересный ответ, но к сожалению из-за того что используются строчные элементы, то рамка очень сильно искажается в зависимости от размера слова и если начать сужать, то в какой-то момент происходит разрыв между словами и рамка делится на 2 части:
.message {
margin-bottom: 16px;
}
.wrapped {
outline: 2px solid black;
}
.wrapped span {
border: 1px solid white;
background-color: white;
position: relative;
z-index: 1000;
}
.img {
height: 100px;
width: 200px;
background-color: red;
float: left;
margin-right: 8px;
}
.img.right {
float: right;
margin-left: 8px;
}
<div>
<div class="img">IMG</div>
<div class="message">
<span class="wrapped">
<span>This is a potentially large paragraph of text, which may get wrapped onto several lines when displayed in the browser. I would like to be able to draw a minimal box round the span This is a potentially large paragraph of text, which may get wrapped onto several lines when displayed in the browser. I would like to be able to draw a minimal box round the span This is a potentially large paragraph of text, which may get wrapped onto several lines when displayed in the browser. I would like to be able to draw a minimal box round the span This is a potentially large paragraph of text, which may get wrapped onto several lines when displayed in the browser. I would like to be able to draw a minimal box round the span This is a potentially large paragraph of text, which may get wrapped onto several lines when displayed in the browser. I would like to be able to draw a minimal box round the span
</span>
</span>
</div>
<div class="img right">IMG</div>
<div class="message">
<span class="wrapped">
<span>This is a potentially large paragraph of text, which may get wrapped onto several lines when displayed in the browser. I would like to be able to draw a minimal box round the span This is a potentially large paragraph of text, which may get wrapped onto several lines when displayed in the browser. I would like to be able to draw a minimal box round the span This is a potentially large paragraph of text, which may get wrapped onto several lines when displayed in the browser. I would like to be able to draw a minimal box round the span This is a potentially large paragraph of text, which may get wrapped onto several lines when displayed in the browser. I would like to be able to draw a minimal box round the span This is a potentially large paragraph of text, which may get wrapped onto several lines when displayed in the browser. I would like to be able to draw a minimal box round the span
</span>
</span>
<div>
</div>
Ответы (2 шт):
Рамку многоугольную сделать - тот еще геморрой. Зато можно симулировать нужное поведение с помощью неразмытых теней:
.img {
height: 100px;
width: 200px;
float: left;
border: 2px solid #000;
box-shadow: 5px 5px 0 5px #fff, 6px 6px 0 6px #000;
margin: 0 calc(12px + 0.5em) 12px 0;
}
.text {
margin-bottom: 8px;
border: 2px solid;
padding: 0.5em;
}
.container {
border: 2px solid;
padding: 0.5em;
background-color: #fff;
}
<div class="container">
<div class="img">IMG</div>
<div class="text">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus expedita, facilis, hic id ipsa ipsam laboriosam libero mollitia nam odio possimus recusandae sed similique tempore unde veniam vitae voluptatem voluptates.Lorem ipsum dolor sit amet,
consectetur adipisicing elit. Accusamus expedita, facilis, hic id ipsa ipsam laboriosam libero mollitia nam odio possimus recusandae sed similique tempore unde veniam vitae voluptatem voluptates.Lorem ipsum dolor sit amet, consectetur adipisicing
elit. Accusamus expedita, facilis, hic id ipsa ipsam laboriosam libero mollitia nam odio possimus recusandae sed similique tempore unde veniam vitae voluptatem voluptates.Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus expedita,
facilis, hic id ipsa ipsam laboriosam libero mollitia nam odio possimus recusandae sed similique tempore unde veniam vitae voluptatem voluptates.
</div>
<div class="text">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus expedita, facilis, hic id ipsa ipsam laboriosam libero mollitia nam odio possimus recusandae sed similique tempore unde veniam vitae voluptatem voluptates.Lorem ipsum dolor sit amet,
consectetur adipisicing elit. Accusamus expedita, facilis, hic id ipsa ipsam laboriosam libero mollitia nam odio possimus recusandae sed similique tempore unde veniam vitae voluptatem voluptates.Lorem ipsum dolor sit amet, consectetur adipisicing
elit. Accusamus expedita, facilis, hic id ipsa ipsam laboriosam libero mollitia nam odio possimus recusandae sed similique tempore unde veniam vitae voluptatem voluptates.Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus expedita,
facilis, hic id ipsa ipsam laboriosam libero mollitia nam odio possimus recusandae sed similique tempore unde veniam vitae voluptatem voluptates.
</div>
</div>
Мне удалось что-то нашаманить. Идея в том, чтобы отступ между текстовыми блоками задавать не с помощью margin, а отдельным блоком, который "разрывает" границу _|, ложась поверх.
:root {
--text-pad: 8px;
--border-size: 2px;
--border: var(--border-size) solid green;
}
.img-left {
float: left;
border-right: var(--border);
border-bottom: var(--border);
margin-right: var(--text-pad);
padding: 0 var(--text-pad) var(--text-pad) 0;
background: white;
}
.img {
height: 100px;
width: 200px;
border: var(--border);
z-index: 2;
position: relative;
}
.text {
padding: 0 var(--text-pad);
background: white;
border: var(--border);
}
.container { padding: 10px; }
.offset {
padding: calc(var(--text-pad) / 2) 0px;
background: white;
z-index: 1;
position: relative;
}
<div class="container">
<div class="img-left">
<div class="img">IMG</div>
</div>
<div class="text">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus expedita, facilis, hic id ipsa ipsam laboriosam libero mollitia nam odio possimus recusandae sed similique tempore unde veniam vitae voluptatem voluptates.Lorem ipsum dolor sit amet
</div>
<div class="offset"></div>
<div class="text">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus expedita, facilis, hic id ipsa ipsam laboriosam libero mollitia nam odio possimus recusandae sed similique tempore unde veniam vitae voluptatem voluptates.Lorem ipsum dolor sit amet,
consectetur adipisicing elit. Accusamus expedita, facilis, hic id ipsa ipsam laboriosam libero mollitia nam odio possimus recusandae sed similique tempore unde veniam vitae voluptatem voluptates.Lorem ipsum dolor sit amet, consectetur adipisicing
elit. Accusamus expedita, facilis, hic id ipsa ipsam laboriosam libero mollitia nam odio possimus recusandae sed similique tempore unde veniam vitae voluptatem voluptates.Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus expedita,
facilis, hic id ipsa ipsam laboriosam libero mollitia nam odio possimus recusandae sed similique tempore unde veniam vitae voluptatem voluptates.
</div>
<div class="offset"></div>
</div>
Тут есть пограничный случай, когда оба текстовых блока меньше по высоте, чем изображение. Но и с этим можно пошаманить...
UPD
Код выше годится для частного случая. Учитывая все пожелания, перспективы подхода туманны.
В вашем примере, как уже было отмечено, можно улучшить результат, добавив text-align: justify в класс .message.
Также хочу поделится решением на базе Canvas и JS. Я сначала всерьез не рассматривал такой вариант, но удалось получить достаточно эффективное решение.
Метод clip() помогает сымитировать булевы операции над прямоугольными областями элементов. Т.е. все текстовые блоки используем как области отсечения, рисуем поверх рамки блоков-картинок, затем их же залитыми без рамки, чтобы затереть лишние линии внутри группы.
const canvas = document.getElementById('outline');
const container = document.querySelector('.container');
const txtBlocks = document.querySelectorAll('.text');
const lImages = document.querySelectorAll('.img-left');
const rImages = document.querySelectorAll('.img-right');
const ctx = canvas.getContext('2d');
let lineSize = 2;
function drawOutline() {
const rc = container.getBoundingClientRect();
canvas.width = rc.width;
canvas.height = rc.height;
ctx.fillStyle = 'white';
ctx.strokeStyle = 'red';
ctx.lineWidth = 2 * lineSize;
for (tb of txtBlocks) {
ctx.rect(tb.offsetLeft, tb.offsetTop, tb.offsetWidth, tb.offsetHeight);
}
ctx.clip();
drawComponents(rImages, true);
drawComponents(lImages, true);
ctx.stroke();
drawComponents(lImages, false);
drawComponents(rImages, false);
}
function drawComponents(rects, stroke) {
let left = rects[0].classList.contains('img-left');
for (im of rects) {
let l = left ? 0 : im.offsetLeft;
let w = left ? im.offsetLeft + im.offsetWidth : canvas.width - l + im.offsetWidth;
if (stroke)
ctx.rect(l, im.offsetTop, w, im.offsetHeight);
else
ctx.fillRect(l, im.offsetTop, w, im.offsetHeight);
}
}
drawOutline();
window.addEventListener('resize', drawOutline);
//
:root {
--text-pad: 8px;
}
#outline {
position: absolute;
left: 0;
top: 0;
z-index: -1;
}
.img-left {
float: left;
margin-right: var(--text-pad);
padding: var(--text-pad);
padding-left: 0;
}
.img-right {
float: right;
margin-left: var(--text-pad);
padding: var(--text-pad);
padding-right: 0;
}
.img {
height: 100px;
width: 100px;
border: 2px solid blue;
}
.small { height: 50px; width: 50px; }
.medium { height: 80px; width: 80px; }
.img-left+.img-left {
margin-left: calc(-1 * var(--text-pad));
}
.img-right+.img-right {
margin-right: calc(-1 * var(--text-pad));
}
.text {
padding: 0 var(--text-pad);
margin-top: var(--text-pad);
}
.container {
padding: 10px;
position: relative;
overflow: hidden;
}
<div class="container">
<canvas id="outline"></canvas>
<div class="img-left"><div class="img">IMG</div></div>
<div class="img-left"><div class="img medium">IMG</div></div>
<div class="img-left"><div class="img small">IMG</div></div>
<div class="img-right"><div class="img medium">IMG</div></div>
<div class="img-right"><div class="img small">IMG</div></div>
<div class="text">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus expedita, facilis, hic id ipsa ipsam laboriosam libero mollitia nam odio possimus recusandae sed similique tempore unde veniam vitae voluptatem voluptates.
</div>
<div class="img-left"><div class="img medium">IMG</div></div>
<div class="img-right"><div class="img small">IMG</div></div>
<div class="text">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus expedita, facilis, hic id ipsa ipsam laboriosam libero mollitia nam odio possimus recusandae sed similique tempore unde veniam vitae voluptatem voluptates.
</div>
</canvas>
Решение, кажется, универсальным. Можно добавить любой набор картинок с разным позиционированием. Текстовые блоки могут разбиваться на несколько изолированных.
Остается две проблемы. Первая – это формирование пустых блоков. Но я не представляю как понять, что текста уже нет. Вторая – это формирование лишних узких полосок. К счастью, есть простое решение. Вводим переменную tolerance и проверяем расстояние от нижней границы рисуемого блока до нижней границы текстового блока. Если расстояние <= tolerance, то рисуем до нижней границы текстового блока, затирая полоску.
const canvas = document.getElementById('outline');
const container = document.querySelector('.container');
const txtBlocks = document.querySelectorAll('.text');
const lImages = document.querySelectorAll('.img-left');
const rImages = document.querySelectorAll('.img-right');
const ctx = canvas.getContext('2d');
let lineSize = 2;
let tolerance = 18;
let toleranceLines = [];
function drawOutline() {
const rc = container.getBoundingClientRect();
canvas.width = rc.width;
canvas.height = rc.height;
ctx.fillStyle = 'white';
ctx.strokeStyle = 'red';
ctx.lineWidth = 2 * lineSize;
toleranceLines = [];
for (tb of txtBlocks) {
ctx.rect(tb.offsetLeft, tb.offsetTop, tb.offsetWidth, tb.offsetHeight);
toleranceLines.push(tb.offsetTop + tb.offsetHeight);
}
ctx.clip();
drawComponents(rImages, true);
drawComponents(lImages, true);
ctx.stroke();
drawComponents(lImages, false);
drawComponents(rImages, false);
}
function drawComponents(rects, stroke) {
let left = rects[0].classList.contains('img-left');
for (im of rects) {
let l = left ? 0 : im.offsetLeft;
let w = left ? im.offsetLeft + im.offsetWidth : canvas.width - l + im.offsetWidth;
let h = im.offsetHeight;
let b = im.offsetTop + h;
let i = toleranceLines.findIndex((v) => Math.abs(b - v) <= tolerance);
if (i !== -1) h += toleranceLines[i] - b;
if (stroke)
ctx.rect(l, im.offsetTop, w, h);
else
ctx.fillRect(l, im.offsetTop, w, h);
}
}
drawOutline();
window.addEventListener('resize', drawOutline);
:root {
--text-pad: 8px;
}
#outline {
position: absolute;
left: 0;
top: 0;
z-index: -1;
}
.img-left {
float: left;
margin-right: var(--text-pad);
padding: var(--text-pad);
padding-left: 0;
}
.img-right {
float: right;
margin-left: var(--text-pad);
padding: var(--text-pad);
padding-right: 0;
}
.img {
height: 100px;
width: 100px;
border: 2px solid blue;
}
.small { height: 50px; width: 50px; }
.medium { height: 80px; width: 80px; }
.img-left+.img-left {
margin-left: calc(-1 * var(--text-pad));
}
.img-right+.img-right {
margin-right: calc(-1 * var(--text-pad));
}
.text {
padding: 0 var(--text-pad);
margin-top: var(--text-pad);
}
.container {
padding: 0 10px;
position: relative;
overflow: hidden;
}
<div class="container">
<canvas id="outline"></canvas>
<div class="img-left"><div class="img">IMG</div></div>
<div class="img-left"><div class="img medium">IMG</div></div>
<div class="img-left"><div class="img small">IMG</div></div>
<div class="img-right"><div class="img medium">IMG</div></div>
<div class="img-right"><div class="img small">IMG</div></div>
<div class="text">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus expedita, facilis, hic id ipsa ipsam laboriosam libero mollitia nam odio possimus recusandae sed similique tempore unde veniam vitae voluptatem voluptates.
</div>
<div class="img-left"><div class="img medium">IMG</div></div>
<div class="img-right"><div class="img small">IMG</div></div>
<div class="text">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus expedita, facilis, hic id ipsa ipsam laboriosam libero mollitia nam odio possimus recusandae sed similique tempore unde veniam vitae voluptatem voluptates.
</div>
</canvas>
Понятно, что tolerance не должен превышать высоту строки текста, иначе будут артефакты.
Теоретически, если оперировать полигонами, а не рисованием на канве, можно частично порешать первую проблему при overflow-wrap: anywhere, или предварительном анализе текста, чтобы с определенной долей вероятности можно было определить, что блок пуст. Но до этого руки пока не дошли.
Надеюсь, кому-нибудь будет полезно.