はてなブログで ResizeObserver と SVG のオーバレイを組み合わせる

はてなブログMarkdown モードでは、HTML を直接書けることが知られているが、記事の中に SVG を直接マークアップしたり <script> タグを書けることはあまり知られていない。

しかし、これらを巧く使うと、たとえば関連する単語間を線で結ぶといったギミックに対応できる。

don't 
trust 
any 
candidate
.
To 
begin 
with, 
they
 
always 
make 
promises 
they
 
don't 
keep 
.

 

さらに、ブラウザの幅が変わって改行位置が変わっても、線の端点の座標が再計算される。

ソース

  <div id="myContainer" style="display: flex; flex-direction: column; position: relative; gap: 1rem;">
    <div style="display: flex; flex-direction: row; flex-wrap: wrap;">
      <div style="flex: 0 0 auto;">I&nbsp;</div>
      <div style="flex: 0 0 auto;">don't&nbsp;</div>
      <div style="flex: 0 0 auto;">trust&nbsp;</div>
      <div style="flex: 0 0 auto;">any&nbsp;</div>
      <div style="flex: 0 0 auto; display: flex; z-index: 1">
        <div style="flex: 0 0 auto" id="candidate">candidate</div>.
      </div>
    </div>
    <div style="display: flex; flex-direction: row; flex-wrap: wrap;">
      <div style="flex: 0 0 auto;">To&nbsp;</div>
      <div style="flex: 0 0 auto;">begin&nbsp;</div>
      <div style="flex: 0 0 auto;">with,&nbsp;</div>
      <div style="flex: 0 0 auto; display: flex; z-index: 1">
        <div style="flex: 0 0 auto;" id="they1">they</div>&nbsp;
      </div>
      <div style="flex: 0 0 auto;">always&nbsp;</div>
      <div style="flex: 0 0 auto;">make&nbsp;</div>
      <div style="flex: 0 0 auto;" id="promises1">promises&nbsp;</div>
      <div style="flex: 0 0 auto; display: flex; z-index: 1">
        <div style="flex: 0 0 auto;" id="they2">they</div>&nbsp;
      </div>
      <div style="flex: 0 0 auto;">don't&nbsp;</div>
      <div style="flex: 0 0 auto;">keep&nbsp;</div>
      <div style="flex: 0 0 auto; border: 1px solid #cccccc;" id="promises2"></div>
      <div style="flex: 0 0 auto;">.</div>
    </div>
    <div id="overlay" style="position: absolute; left:0; right:0; top:0; bottom:0;">
      <svg id="svg" x=0 y=0>
        <line id="line1" stroke="#cccc00" stroke-width="2" />
        <line id="line2" stroke="#cccc00" stroke-width="2" />
      </svg>
    </div>
  </div>
  <script>
    
    const resizeObserver = new ResizeObserver(entries => {
      const promises1 = document.getElementById('promises1');
      const promises2 = document.getElementById('promises2');
      promises2.style.width = `${promises1.clientWidth}px`;

      const overlay = document.getElementById('overlay');
      const svg = document.getElementById('svg');

      svg.setAttribute('width', `${overlay.clientWidth}`);
      svg.setAttribute('height', `${overlay.clientHeight}`);

      const candidate = document.getElementById('candidate');
      const x1 = candidate.offsetLeft + candidate.clientLeft + candidate.clientWidth * 0.5;
      const y1 = candidate.offsetTop + candidate.clientTop + candidate.clientHeight * 0.5;

      candidate.style.backgroundColor = 'white';
      candidate.style.border = '1px solid #cccc00';

      const they1 = document.getElementById('they1');
      const x2 = they1.offsetLeft + they1.clientLeft + they1.clientWidth * 0.5;
      const y2 = they1.offsetTop + they1.clientTop + they1.clientHeight * 0.5;

      const they2 = document.getElementById('they2');
      const x3 = they2.offsetLeft + they2.clientLeft + they2.clientWidth * 0.5;
      const y3 = they2.offsetTop + they2.clientTop + they2.clientHeight * 0.5;

      they1.style.backgroundColor = 'white';
      they1.style.border = '1px solid #cccc00';

      they2.style.backgroundColor = 'white';
      they2.style.border = '1px solid #cccc00';

      const line1 = document.getElementById('line1');
      line1.setAttribute('x1', x1);
      line1.setAttribute('y1', y1);
      line1.setAttribute('x2', x2);
      line1.setAttribute('y2', y2);

      const line2 = document.getElementById('line2');
      line2.setAttribute('x1', x1);
      line2.setAttribute('y1', y1);
      line2.setAttribute('x2', x3);
      line2.setAttribute('y2', y3);
    });
    resizeObserver.observe(document.getElementById('myContainer'));
  </script>

この文字列に対して、(^\s+)|(\n)'' に置換して、余分なインデントや改行を消し、Markdown モード or はてなブログモードの編集エリアにペーストする。