5
votes

Faire redimensionner dynamiquement les enfants positionnés en absolu avec leurs ascendants

(Veuillez ignorer les carrés vides.)

  1. sans view { height: 45em; } CSS view { height: 45em; } , J'obtiens: entrez la description de l'image ici (chevauchement de position)
  2. avec view { height: 45em; } CSS view { height: 45em; } , J'obtiens: entrez la description de l'image ici (non désiré, décalage de position)

Comment puis-je positionner correctement l'élément bleu <span> dans le deuxième cas?

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

Voici le cas complet dans stackoverflow (voir /* ↠*/ dans le deuxième volet après avoir cliqué sur Afficher l'extrait de code):

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

(également disponible pour examen sur codepen: https://codepen.io/cetinsert/pen/MWeVxLe?editors=1100 )


2 commentaires

Pouvez-vous expliquer un peu plus ce que vous essayez de faire? Vous essayez de faire chevaucher la boîte grise les boîtes bleues?


Modification de la question pour plus de clarté. Je veux que le chevauchement des cases noir-bleu soit préservé exactement de la même manière dans le second cas que dans le premier.


3 Réponses :


0
votes

Compte tenu de la width et de la height fenêtre, une conversion unique de <span style> pixels en pourcentages :

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

et prise en compte de l'adaptation de la taille de la police dans l'élément <text> chaque fois que ses ancêtres provoquent un redimensionnement:

const textFontResize = t => ([ a ]) => {
  const i = t.parentNode.lastElementChild; // <svg> | <canvas>
            t.style.setProperty('font-size', `${i.clientHeight}px`);
};
const t = document.querySelector('text');
const r = new ResizeObserver(textFontResize(t));
      r.observe(t);

s'est avérée une solution très robuste et relativement simple.

(Si quelqu'un propose une manière plus élégante, par exemple, sans jamais recourir à ResizeObserver , veuillez publier une nouvelle réponse.)


Démo

(Les ressources externes sont corrigées pour cette question.)

  1. Faites défiler jusqu'à la fin de cette réponse
  2. Hit â – ¶ï¸ Exécuter l'extrait de code
  3. Hit ⤤ Pleine page

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


2 commentaires

Si vous demandez: "Can CSS read current values, and transform them into other values?" , alors non, CSS ne peut pas faire ça, vous aurez besoin de JavaScript pour cela. (Ce que vous avez déjà fait.). Et donc si vous voulez utiliser du CSS pur, vous devrez créer vos propres nombres à la main, puisque vous essayez de placer un bloc au-dessus d'un point "arbitraire" sur une image.


@EliezerBerlin - Après avoir accepté l'utilisation de ResizeObserver , j'ai trouvé une réponse beaucoup plus simple stackoverflow.com/a/64740559/112882 qui ne touche pas du tout aux éléments <span> .



-1
votes

Ce n'est pas vraiment possible de le faire via CSS de manière propre. Ceci, par exemple, fonctionnera, mais comme vous placez une plage au-dessus d'une image, tous les nombres sont codés en dur:

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

Voici une reproduction de votre extrait avec le CSS ajouté:

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

Edit: Un peu plus de clarification.

Nous voulons mettre la boîte au-dessus du texte. Les nombres utilisés pour la position et la largeur / hauteur du texte peuvent sembler arbitraires, mais c'est simplement parce que l'emplacement de l'élément que nous essayons de couvrir a AUSSI un emplacement / largeur / hauteur arbitraire. (Si vous le souhaitez, nous pouvons vous expliquer comment utiliser GIMP pour vérifier le rapport hauteur / largeur de votre image, mais ..

une. Je ne pense pas que l'utilisation de GIMP pour mesurer les valeurs correctes entre dans le cadre de cette réponse (vous pouvez le calculer en prenant la largeur de l'image et la hauteur de l'image pour trouver le rapport hauteur / largeur, puis utiliser ce rapport hauteur / largeur le long avec les coordonnées X / Y du point de départ et les coordonnées X / Y du point final pour déterminer les pourcentages que vous devez utiliser ... mais bon ...)

b. Il est généralement beaucoup plus rapide de le manipuler dans les outils de développement de Chrome pendant 15 minutes,

En règle générale, lorsque vous utilisez position: absolute pour mettre quelque chose au-dessus d'une image, votre code va ressembler à quelque chose comme ceci:

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

Edit 2: J'avais initialement utilisé vw et vh , qui sont souvent extrêmement utiles pour ce type de positionnement, mais à la fin, il était possible de les refactoriser, c'est pourquoi le seul positionnement non standard que nous utilisons est margin-top .


1 commentaires

Je veux dire ... vous mentionnez des unités autres que % et px mais utilisez toujours % . Comment calculez-vous également les valeurs de ces propriétés CSS arbitrairement choisies ( right vs left , margin-top , ...)?



3
votes

Une manière beaucoup plus précise est de simplement transform: scale(x, y) le calque <text> une fois lors du redimensionnement sans recalcul de la valeur de position <span style> / changement d'unité.

(Les éléments ci-dessous et d'autres trouvent leur chemin dans une solution PDF native pour le Web: https://WebPDF.dev .)

.textLayer { overflow: visible; }
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 };
  }    
};
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})`);
};

avec 1 règle CSS supplémentaire

const t = document.querySelector('text');
const r = new ResizeObserver(textResize(t));
      r.observe(t);

Avant après

avant après


0 commentaires