(Veuillez ignorer les carrés vides.)
view { height: 45em; }
CSS view { height: 45em; }
, J'obtiens: view { height: 45em; }
CSS view { height: 45em; }
, J'obtiens: 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 )
3 Réponses :
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.)
(Les ressources externes sont corrigées pour cette question.)
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}%`); };
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>
.
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
.
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
, ...)?
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);
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.