使绝对定位的孩子随他们的上级动态调整大小

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】:

给定视口widthheight,从&lt;span style&gt; 像素百分比一次性转换

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%`);
;

并在 &lt;text&gt; 元素的祖先导致调整大小时考虑字体大小调整:

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,它根本不涉及&lt;span&gt; 元素。【参考方案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:我最初使用vwvh,这对于这种定位通常非常有用,但最终可以将它们重构出来,这就是为什么唯一的非标准我们使用的定位是margin-top

【讨论】:

我的意思是......你提到了%px以外的单位,但仍然使用%。另外,您如何计算那些任意选择的 CSS 属性的值(right vs leftmargin-top、...)?【参考方案3】:

更精确的方法是在调整大小时只对 transform: scale(x, y) &lt;text&gt; 层进行一次调整,而不需要重新计算 &lt;span style&gt; 位置值/单位更改。


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

– 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层位置如何保持不变

CSS DIV绝对定位 后 页面的大小改变 div层位置如何保持不变

CSS相对定位与绝对定位详解