使绝对定位的子项随其上升项动态调整大小

问题描述 投票:0回答:3

(请忽略空方块。)

  1. 没有 CSS
    view { height: 45em; }
    ,我得到:(位置重叠)
  2. 使用 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 width="595px" height="842px" 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>

这是 stackoverflow 中的完整案例(单击“显示代码片段”后,请参阅第二个窗格中的

/* ← */
):

@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" width="595px" height="842px" 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

html css css-position pdf.js pdfium
3个回答
3
投票

更精确的方法是在调整大小时仅对

transform: scale(x, y)
层进行一次调整,而不进行任何
<text>
位置值重新计算/单位更改。

这个答案触发了我的商业项目的启动。

https://WebPDF.pro

零依赖、真正的 HTML 原生 PDF Web 组件。

<span style>
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})`);
};
有 1 条额外的 CSS 规则

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 }; } };

之前/之后


0
投票
.textLayer { overflow: visible; }

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.clientHeight}px`); };

,请发布新答案。)


演示

(此问题的外部资产版本已固定。)

滚动到此答案的末尾
  1. 点击
  2. ▶️运行代码片段
  3. 点击
  4. ⤤整页

ResizeObserver


-1
投票

<!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>

这是添加了 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%; }
@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); }

编辑:更多说明。

我们想将框放在文本上方。用于文本位置和宽度/高度的数字可能看起来是任意的,但这只是因为我们试图覆盖的项目的位置也具有任意位置/宽度/高度。 (如果您愿意,我们可以讨论如何使用 GIMP 检查图像的长宽比,但是..

a.我认为使用 GIMP 来测量正确的值不在这个答案的范围内(您可以通过计算图像的宽度和图像的高度来找到纵横比,然后使用该纵横比用起点的 X/Y 坐标和终点的 X/Y 坐标来计算出您需要使用的百分比......但是,好吧......)

b.在 Chrome 的开发工具中摆弄它 15 分钟通常会

明显

更快, 作为一般规则,当使用

<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" width="595px" height="842px" 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>

在图像顶部放置某些内容时,您的代码将如下所示:

position: absolute

编辑2:我最初使用
.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%; }

vw
,它们对于这种定位通常非常有用,但最终可以将它们重构出来,这就是为什么我们唯一的非标准定位使用的是
vh
    

© www.soinside.com 2019 - 2024. All rights reserved.