使绝对定位的孩子随他们的上级动态调整大小
Posted
技术标签:
【中文标题】使绝对定位的孩子随他们的上级动态调整大小【英文标题】:Make absolute-positioned children dynamically resize with their ascendants 【发布时间】:2021-02-13 16:26:44 【问题描述】:(请忽略空格。)
-
没有 CSS
view height: 45em;
,我得到:(位置重叠)
使用 CSS view height: 45em;
,我得到:(不需要,位置不匹配)
如何在第二种情况下正确定位蓝色 <span>
元素?
<view style="height: 45em;">
<pdf-page> <!-- position: relative -->
<text class="textLayer"> <!-- position: absolute -->
<span style="left: 417.34px; top: 37.8391px; ..."></span> <!-- position: absolute -->
</text>
<svg preserveAspectRatio="none" viewBox="0 0 595 842" xmlns="http://www.w3.org/2000/svg" version="1.1">
<g ⋯><g ⋯><text><tspan></tspan></text></g></g>
</svg>
</pdf-page>
</view>
这是 *** 中的完整案例(点击 Show code sn-p 后在第二个窗格中查看 /* ← */
):
@namespace url(http://www.w3.org/1999/xhtml);
@namespace svg url(http://www.w3.org/2000/svg);
/*pdf.css*/
:root
--pdf-page-outline-color: #aaa;
--pdf-page-background-color: #fcfcfc;
pdf-file display: contents;
pdf-page
display: inline-block;
outline: 1px solid var(--pdf-page-outline-color);
background-color: var(--pdf-page-background-color);
pdf-page position: relative;
/* text.css */
.textLayer
position: absolute;
left: 0; top: 0; right: 0; bottom: 0;
width: 100%; height: 100%;
-overflow: hidden;
opacity: 1;
-line-height: 1;
.textLayer > span
color: transparent;
position: absolute;
white-space: pre;
cursor: text;
-webkit-transform-origin: 0% 0%;
transform-origin: 0% 0%;
/**/
view background: green;
.textLayer background: rgba(0, 255, 0, .1);
svg|svg background: rgba(255, 0, 0, .1);
<style>
view
height: 45em; /* ← */
display: flex;
overflow: auto;
flex-direction: column;
place-items: center;
scroll-snap-type: y mandatory;
overflow: auto;
pdf-page height: 100%; scroll-snap-align: start;
svg height: 100%; width: auto;
text overflow: visible; background: rgb(0, 0, 0, .1);
text > span background: rgba(0,0,255,.1);
</style>
<view -onclick="this.requestFullscreen()">
<pdf-page of="f" no="+1" svg="">
<text class="textLayer">
<span style="left: 417.34px; top: 37.8391px; font-size: 12px; font-family: sans-serif; transform: scaleX(1.07482);">Plenarprotokoll 16/3</span>
</text>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" preserveAspectRatio="none" viewBox="0 0 595 842">
<g transform="matrix(1 0 0 -1 -8 850)">
<g transform="">
<text transform="matrix(12 0 0 12 425.34 801.2976) scale(1, -1)" xml:space="preserve">
<tspan x="0 0.6672 0.9454 1.5016 2.1128 2.669 3.0582 3.6694 4.0586 4.6698 5.003 5.6142 6.1704 6.7816 7.0598 7.6132 8.1694 8.7256 9.0038" y="0" font-family="g_d0_f1" font-size="1px" fill="rgb(0,0,0)"></tspan>
</text>
</g>
</g>
</svg>
</pdf-page>
</view>
(也可在 codepen 上查看:https://codepen.io/cetinsert/pen/MWeVxLe?editors=1100)
【问题讨论】:
你能解释一下你想要做什么吗?您想让灰色框与蓝色框重叠吗? 为清楚起见编辑了问题。我希望在第二种情况下以与第一种情况完全相同的方式保留黑色 - 蓝色框重叠。 【参考方案1】:给定视口width
和height
,从<span style>
像素到百分比的一次性转换:
const px2pc = ( width, height ) => s =>
const c = s.style;
const l = +c.getPropertyValue('left' ).slice(0, -2); // drop px
const t = +c.getPropertyValue('top' ).slice(0, -2);
const f = +c.getPropertyValue('font-size').slice(0, -2);
c.setProperty ('left', `$(l / width) * 100%`);
c.setProperty ('top', `$(t / height) * 100%`);
c.setProperty ('font-size', `$(f / height) * 100%`);
;
并在 <text>
元素的祖先导致调整大小时考虑字体大小调整:
const t = document.querySelector('text');
const r = new ResizeObserver(textFontResize(t));
r.observe(t);
const textFontResize = t => ([ a ]) =>
const i = t.parentNode.lastElementChild; // <svg> | <canvas>
t.style.setProperty('font-size', `$i.clientHeightpx`);
;
证明自己是一个非常强大且相对简单的解决方案。
(如果有人想出更优雅的方式,请不要诉诸ResizeObserver
,请发布新答案。)
演示
(此问题的外部资产版本已修复。)
-
滚动到此答案的末尾
点击▶️运行代码sn-p
点击⤤ Full page
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="//cdnjs.cloudflare.com/ajax/libs/pdf.js/2.6.347/pdf.min.js" integrity="sha512-Z8CqofpIcnJN80feS2uccz+pXWgZzeKxDsDNMD/dJ6997/LSRY+W4NmEt9acwR+Gt9OHN0kkI1CTianCwoqcjQ==" crossorigin="anonymous"></script>
<script src="//shin.glitch.me/shin.q1.js"></script>
<script src="//shin.glitch.me/pdf.q1.js"></script>
<!-- private resources -->
<link href="//cdn.blue/fa-5.15/css/all.css" rel="stylesheet">
<link href="//cdn.blue/fa+/var.css" rel="stylesheet">
<link href="//cdn.blue/fa+/x.css" rel="stylesheet">
<!-- private resources -->
<style>:root max-width: 50em; margin: auto; </style>
<script>console.clear();</script>
<style>html, body padding: 0; margin: 0; font-family: system-ui; </style>
<script>
class CodeEditElement extends ShinElement
constructor()
super(`
<style>:host display: block; overflow: hidden; pre height: 100%; margin: 0; </style>
<pre contenteditable spellcheck=false inputmode=text></pre>`, _: QS: T: [ 'pre' ] );
const e = this;
e.ph = v => const e = v.target; if (!e.childElementCount) return; e.textContent = e.textContent; ;
connectedCallback() this._.pre. addEventListener('input', this.ph);
disconnectedCallback() this._.pre.removeEventListener('input', this.ph);
get textContent() return this._.pre.textContent;
set textContent(v) this._.pre.textContent = v;
CodeEditElement.define();
class CodeLiveElement extends ShinElement
constructor() super(`<live></live>`, _: QS: T: [ 'live' ] );
get textContent() return this._.live.textContent;
set textContent(v) this._.live.textContent = v;
get innerHTML() return this._.live.innerHTML;
set innerHTML(v) this._.live.innerHTML = v; this.evalScripts();
evalScripts() this._.QA('script').forEach(s => eval(s.textContent));
CodeLiveElement.define();
class CodePlayElement extends ShinElement
constructor()
super(`
<style>
:host(:not([open])) > code-edit display: none;
:host > div display: flex; justify-content: stretch; align-items: stretch;
::slotted(select) flex: 1;
* border-color: var(--bd);
</style>
<div part=controls>
<slot></slot>
<button id=reset><slot name=reset></slot></button>
<button id=open><slot name=open></slot></button>
</div>
<code-edit id=pre part=edit></code-edit>`,
_: QS: S: '#pre': 'pre', '#reset': 'reset', '#open': 'open'
);
const e = this;
e.sc = v => const tx = e.tx; e.px = tx; ;
e.pi = v => e.t.ux = e.px; ;
e.rc = v => e.tr(); ;
e.oc = v => e.open =!e.open; ;
Shin.IPA(e, 'open', t: Shin.Boolean );
connectedCallback() setTimeout(() => this._init());
disconnectedCallback() this._dest();
static cleanCode(t = "")
return t.trim().replace(/^[\n]+/g, '').replace(/\s+$/g, '').split('\n').map(l => l.trimEnd()).join('\n');
get s() return this.QS('select');
get S() const o = this.QA('option'); return o.filter(o => o.selected);
get t() return [].concat(...this.S.map(o => Shin.QA('template', o)));
get tx() return this.t.map(t => t.ux || this.constructor.cleanCode(t.innerHTML)).join('\n');
tr() this.t.ux = undefined; this.sc();
get r() return this._.reset;
get o() return this._.open;
get p() return this._.pre;
get P() return this._.QA('pre');
get px() return this.p.textContent;
set px(v) this.p.textContent = v; this.oninput();
_init()
const e = this; e.sc();
e.s.addEventListener('change', e.sc);
e.p.addEventListener('input', e.pi);
e.r.addEventListener('click', e.rc);
e.o.addEventListener('click', e.oc);
_dest()
const e = this;
e.s.removeEventListener('change', e.sc);
e.p.removeEventListener('input', e.pi);
e.p.removeEventListener('click', e.rc);
e.p.removeEventListener('click', e.oc);
CodePlayElement.define();
</script>
<style>
body padding: 1em; overflow: scroll; font-family: system-ui;
:root
--list-bg: #eee;
--code-bg: #fefefe;
--live-bg_: #ccc;
--bd: #ccc;
code-play display: flex; width: 100%; flex-direction: row-reverse;
code-play:not(:first-of-type) margin-top: 1em;
::part(edit) min-height: 1em; min-width: 1em; overflow-x: auto; background-color: var(--code-bg);
x[undo]:before, x[undo]:after content: var(--fa-undo);
x[open]:before, x[open]:after content: var(--fa-eye-slash);
[open] x[open]:before,
[open] x[open]:after content: var(--fa-eye);
select background: var(--list-bg); border-color: var(--bd); overflow: auto;
live background: var(--live-bg); display: block; bordxer: 1px solid var(--bd);
code-play:not([open]) + live _display: none;
::part(edit) border: 1px solid var(--bd); flex: 1;
::part(controls) flex-direction: column-reverse;
::part() border-radius: 3px;
</style>
<style>
code-play:not([open]) height: 2.7em; _outline: 1px solid;
code-play:not([open]) > select display: none;
</style>
</head>
<body>
<code-live id="cl"></code-live><script>cl.innerHTML = "";</script>
<script>
const pes = 'previousElementSibling';
const n = (p, N = 1) => e => let j = e[p]; for (let i = 1; i < N; i++) j = j[p]; return j; ;
const c = n(pes, 2);
const l = n(pes, 1);
const _ = () => document.currentScript;
</script>
<code-play open>
<select multiple size="1">
<option selected>file<template>
<pdf-file id="f" src="//pdf.systems/16003.pdf"></pdf-file>
<pdf-file id="g" src="//pdf.systems/16004.pdf"></pdf-file>
</template></option>
</select>
<x open slot="open"></x>
<x undo slot="reset"></x>
</code-play>
<live></live>
<script>
const _ = document.currentScript;
c(_).oninput = () =>
l(_).innerHTML = c(_).px;
</script>
<code-play open style="min-height: 11em;">
<select multiple size="6">
<optgroup label="File Reference">
<option>by attribute<!-- !!!!!!!!! --><template>
<pdf-page of="f" no="+1" scale=".1"></pdf-page>
<pdf-page of="f" no="+1" scale=".2"></pdf-page>
<pdf-page of="f" no="+1" scale=".3"></pdf-page>
<pdf-page of="f" no="+1" scale=".4"></pdf-page>
<pdf-page of="f" no="+1" scale=".5"></pdf-page>
<pdf-page of="f" no="+1" scale=".5" svg=""></pdf-page>
</template></option>
<option>by ancestry<!-- !!!!!!!!! --><template>
<pdf-file src="//pdf.systems/16008.pdf">
<pdf-page no="+1" scale=".4" svg></pdf-page>
<pdf-page no="+3" scale=".4" svg></pdf-page>
<pdf-page no="-1" scale=".4" svg></pdf-page>
</pdf-file>
</template></option>
</optgroup>
<optgroup label="Embed Mode">
<option selected>Sized Container ⭤<!-- !!!!!!!!! --><template>
<style>
view width: 10em; height: 25em; /* ← */
display: block; background: white; overflow: auto;
pdf-page width: 100%;
::part(layer) width: 100%; height: auto;
</style>
<view onclick="this.requestFullscreen()">
<pdf-page of="f" no="+1" xvg="" scale=".2"></pdf-page>
<pdf-page of="f" no="+1" xvg="" scale="1"></pdf-page>
<pdf-page of="f" no="+1" xvg="" scale="2"></pdf-page>
<pdf-page of="f" no="+1" svg=""></pdf-page>
<pdf-page of="f" no="-1" xvg=""></pdf-page>
<pdf-page of="g" no="-1" svg=""></pdf-page>
</view>
</template></option>
</optgroup>
</select>
<x open slot="open"></x>
<x undo slot="reset"></x>
</code-play>
<live></live>
<script> const _ = document.currentScript; c(_).oninput = () => l(_).innerHTML = c(_).px; </script>
<style>live display: flex; align-items: flex-end; flex-wrap: wrap; pdf-file display: contents; </style>
<h3>Styling</h3>
<p>Styles can be easily applied. (Try <strong><kbd>Ctrl</kbd></strong> + <i class="fa fa-mouse-pointer"></i> to unselect / select multiple.)</p>
<code-play open>
<select multiple size="8">
<optgroup label="Page">
<option>outline <template><style>pdf-page outline: 1px dotted; </style></template></option>
<option>background<template><style>pdf-page background-color: rgb(200, 200, 255, .1); </style></template></option>
</optgroup>
<optgroup label="Text">
<option selected>mark<template><style>::part(span) background-color: rgb(255, 0, 0, .1); </style></template></option>
</optgroup>
<optgroup label="Image">
<option>hidden <template><style>::part(image) opacity: 0; </style></template></option>
<option>pixelated <template><style>::part(image) image-rendering: pixelated; </style></template></option>
<option>crisp-edges<template><style>::part(image) image-rendering: crisp-edges; </style></template></option>
</optgroup>
</select>
<x open slot="open"></x>
<x undo slot="reset"></x>
</code-play>
<live></live>
<script> const _ = document.currentScript; c(_).oninput = () => l(_).innerHTML = c(_).px; </script>
<script>
document.addEventListener( 'load', e => console.warn('l', e.target));
document.addEventListener('unload', e => console.warn('u', e.target));
</script>
<p style="margin-bottom: 10em;"><a href="https://shin.glitch.me/pa.html">Documentation (WIP)</a></p>
</body>
</html>
【讨论】:
如果你问,"Can CSS read current values, and transform them into other values?"
,那么不,CSS 不能这样做,你需要 javascript。 (您已经完成了。)。因此,如果您想使用纯 CSS,则需要自己手动计算数字,因为您试图在图像上的“任意”点上放置一个块。
@EliezerBerlin - 在接受了ResizeObserver
的使用后,我想出了一个更简单的答案***.com/a/64740559/112882,它根本不涉及<span>
元素。【参考方案2】:
通过 CSS 以一种干净的方式进行操作实际上是不可能的。例如,这会起作用,但是因为您将跨度定位在图片之上,所以所有数字都是硬编码的:
.textLayer > span
right: 10% !important;
left: auto !important;
top: 0 !important;
margin-top: 6%;/*margin-top uses 6% of the WIDTH, not 6% of the height. It's very useful when trying to place something on top of an image.*/
width: 20%;
height: 2%;
这是您的 sn-p 的复制品,其中添加了 CSS:
@namespace url(http://www.w3.org/1999/xhtml);
@namespace svg url(http://www.w3.org/2000/svg);
/*pdf.css*/
:root
--pdf-page-outline-color: #aaa;
--pdf-page-background-color: #fcfcfc;
pdf-file display: contents;
pdf-page
display: inline-block;
outline: 1px solid var(--pdf-page-outline-color);
background-color: var(--pdf-page-background-color);
pdf-page position: relative;
/* text.css */
.textLayer
position: absolute;
left: 0; top: 0; right: 0; bottom: 0;
width: 100%; height: 100%;
-overflow: hidden;
opacity: 1;
line-height: 1;
.textLayer > span
color: transparent;
position: absolute;
white-space: pre;
cursor: text;
-webkit-transform-origin: 0% 0%;
transform-origin: 0% 0%;
.textLayer > span
right: 10% !important;
left: auto !important;
top: 0 !important;
margin-top: 6%;/*margin-top uses 6% of the WIDTH, not the height. It's sometimes more useful than ordinary top:6%.*/
width: 20%;
height: 2%;
/**/
view background: green;
.textLayer background: rgba(0, 255, 0, .1);
svg|svg background: rgba(255, 0, 0, .1);
<style>
view
height: 45em; /* ← */
display: flex;
overflow: auto;
flex-direction: column;
place-items: center;
scroll-snap-type: y mandatory;
overflow: auto;
pdf-page height: 100%; scroll-snap-align: start;
svg height: 100%; width: auto;
text overflow: visible; background: rgb(0, 0, 0, .1);
text > span background: rgba(0,0,255,.1);
</style>
<view -onclick="this.requestFullscreen()">
<pdf-page of="f" no="+1" svg="">
<text class="textLayer">
<span style="left: 417.34px; top: 37.8391px; font-size: 12px; font-family: sans-serif; transform: scaleX(1.07482);">Plenarprotokoll 16/3</span>
</text>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" preserveAspectRatio="none" viewBox="0 0 595 842">
<g transform="matrix(1 0 0 -1 -8 850)">
<g transform="">
<text transform="matrix(12 0 0 12 425.34 801.2976) scale(1, -1)" xml:space="preserve">
<tspan x="0 0.6672 0.9454 1.5016 2.1128 2.669 3.0582 3.6694 4.0586 4.6698 5.003 5.6142 6.1704 6.7816 7.0598 7.6132 8.1694 8.7256 9.0038" y="0" font-family="g_d0_f1" font-size="1px" fill="rgb(0,0,0)"></tspan>
</text>
</g>
</g>
</svg>
</pdf-page>
</view>
编辑:再澄清一点。
我们希望将框放在文本的顶部。用于文本位置和宽度/高度的数字可能看起来是任意的,但这仅仅是因为我们试图覆盖的项目的位置也具有任意位置/宽度/高度。 (如果你愿意,我们可以讨论如何使用 GIMP 来检查图像的纵横比,但是..
一个。我认为使用 GIMP 测量正确值不在此答案的范围内(您可以通过计算图像的宽度和图像的高度来找到纵横比,然后使用该纵横比)使用起点的 X/Y 坐标和终点的 X/Y 坐标来确定您需要使用的百分比......但是,好吧....)
b.在 Chrome 的开发工具中摆弄 15 分钟通常会明显更快,
作为一般规则,当使用position: absolute
在图像上放置一些东西时,您的代码将如下所示:
.item
position:absolute;
top:0;
margin-top:W%; //The reason we use margin instead of top is because margin is based off width, which allows us to maintain aspect ratio on our positioning.
left:X%; // Or right
width:Y%;
height:Z%;
编辑2:我最初使用vw
和vh
,这对于这种定位通常非常有用,但最终可以将它们重构出来,这就是为什么唯一的非标准我们使用的定位是margin-top
。
【讨论】:
我的意思是......你提到了%
和px
以外的单位,但仍然使用%
。另外,您如何计算那些任意选择的 CSS 属性的值(right
vs left
、margin-top
、...)?【参考方案3】:
更精确的方法是在调整大小时只对 transform: scale(x, y)
<text>
层进行一次调整,而不需要重新计算 <span style>
位置值/单位更改。
这个答案触发了我的商业项目的启动。
– https://WebPDF.pro
零依赖,真正的 HTML 原生 PDF Web 组件。
const t = document.querySelector('text');
const r = new ResizeObserver(textResize(t));
r.observe(t);
const textResize = t => ([ a ]) =>
const e = t.parentNode.lastElementChild; // <svg> | <canvas>
const i = PDFPageElement.image(e); // height, width ;
const h = e.clientHeight;
const x = h / i. height;
const w = e.clientWidth;
const y = w / i. width;
t.style.setProperty('transform', `scale($x, $y)`);
;
PDFPageElement.image = i => if (!i) return;
switch (i.tagName)
case 'CANVAS': return height: i.height, width: i.width ;
default: /*SVG*/ return height: i.height.baseVal.value, width: i.width.baseVal.value ;
;
附加 1 条 CSS 规则
.textLayer overflow: visible;
之前/之后
【讨论】:
以上是关于使绝对定位的孩子随他们的上级动态调整大小的主要内容,如果未能解决你的问题,请参考以下文章
请问js高手,我想做一个动态的消息提示框,但是用绝对定位的提示框会随滚动条的移动被遮挡?
CSS DIV绝对定位 后 页面的大小改变 div层位置如何保持不变