<div data-reading-assist>
  <p><strong>こちらは<a href="https://zenn.dev/ixkaito/articles/web-reading-assist" target="_blank" rel="noopener">「読書アシストの実装方法について考えてみる」</a>のデモです。幅640ピクセル以上の画面でアシストが有効になります。</strong></p>
  <p>吾輩は猫である。名前はまだ無い。</p>
  <p>どこで生れたかとんと見当がつかぬ。何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。吾輩はここで始めて人間というものを見た。しかもあとで聞くとそれは書生という人間中で一番獰悪な種族であったそうだ。この書生というのは時々我々を捕えて煮て食うという話である。しかしその当時は何という考もなかったから別段恐しいとも思わなかった。ただ彼の掌に載せられてスーと持ち上げられた時何だかフワフワした感じがあったばかりである。掌の上で少し落ちついて書生の顔を見たのがいわゆる人間というものの見始であろう。この時妙なものだと思った感じが今でも残っている。第一毛をもって装飾されべきはずの顔がつるつるしてまるで薬缶だ。その後猫にもだいぶ逢ったがこんな片輪には一度も出会わした事がない。のみならず顔の真中があまりに突起している。そうしてその穴の中から時々ぷうぷうと煙を吹く。どうも咽せぽくて実に弱った。これが人間の飲む煙草というものである事はようやくこの頃知った。</p>
  <p>この書生の掌の裏でしばらくはよい心持に坐っておったが、しばらくすると非常な速力で運転し始めた。書生が動くのか自分だけが動くのか分らないが無暗に眼が廻る。胸が悪くなる。到底助からないと思っていると、どさりと音がして眼から火が出た。それまでは記憶しているがあとは何の事やらいくら考え出そうとしても分らない。</p>
  <p>ふと気が付いて見ると書生はいない。たくさんおった兄弟が一疋も見えぬ。肝心の母親さえ姿を隠してしまった。その上今までの所とは違って無暗に明るい。眼を明いていられぬくらいだ。はてな何でも容子がおかしいと、のそのそ這い出して見ると非常に痛い。吾輩は藁の上から急に笹原の中へ棄てられたのである。</p>
  <p>ようやくの思いで笹原を這い出すと向うに大きな池がある。吾輩は池の前に坐ってどうしたらよかろうと考えて見た。別にこれという分別も出ない。しばらくして泣いたら書生がまた迎に来てくれるかと考え付いた。ニャー、ニャーと試みにやって見たが誰も来ない。そのうち池の上をさらさらと風が渡って日が暮れかかる。腹が非常に減って来た。泣きたくても声が出ない。仕方がない、何でもよいから食物のある所まであるこうと決心をしてそろりそろりと池を左りに廻り始めた。どうも非常に苦しい。そこを我慢して無理やりに這って行くとようやくの事で何となく人間臭い所へ出た。ここへ這入ったら、どうにかなると思って竹垣の崩れた穴から、とある邸内にもぐり込んだ。縁は不思議なもので、もしこの竹垣が破れていなかったなら、吾輩はついに路傍に餓死したかも知れんのである。一樹の蔭とはよく云ったものだ。この垣根の穴は今日に至るまで吾輩が隣家の三毛を訪問する時の通路になっている。さて邸へは忍び込んだもののこれから先どうして善いか分らない。そのうちに暗くなる、腹は減る、寒さは寒し、雨が降って来るという始末でもう一刻の猶予が出来なくなった。仕方がないからとにかく明るくて暖かそうな方へ方へとあるいて行く。今から考えるとその時はすでに家の内に這入っておったのだ。ここで吾輩は彼の書生以外の人間を再び見るべき機会に遭遇したのである。第一に逢ったのがおさんである。これは前の書生より一層乱暴な方で吾輩を見るや否やいきなり頸筋をつかんで表へ抛り出した。いやこれは駄目だと思ったから眼をねぶって運を天に任せていた。しかしひもじいのと寒いのにはどうしても我慢が出来ん。吾輩は再びおさんの隙を見て台所へ這い上った。すると間もなくまた投げ出された。吾輩は投げ出されては這い上り、這い上っては投げ出され、何でも同じ事を四五遍繰り返したのを記憶している。その時におさんと云う者はつくづくいやになった。この間おさんの三馬を偸んでこの返報をしてやってから、やっと胸の痞が下りた。吾輩が最後につまみ出されようとしたときに、この家の主人が騒々しい何だといいながら出て来た。下女は吾輩をぶら下げて主人の方へ向けてこの宿なしの小猫がいくら出しても出しても御台所へ上って来て困りますという。主人は鼻の下の黒い毛を撚りながら吾輩の顔をしばらく眺めておったが、やがてそんなら内へ置いてやれといったまま奥へ這入ってしまった。主人はあまり口を聞かぬ人と見えた。下女は口惜しそうに吾輩を台所へ抛り出した。かくして吾輩はついにこの家を自分の住家と極める事にしたのである。</p>
</div>
// Customize
@media not all and (min-width: 640px) {
  body {
    --assist-indent-max: 0;
  }
}

// Reading Assist
:root {
  --line-height: 2em;
  --assist-indent-max: 7em;
  --assist-lines-first: 7;
  --assist-lines-after: 5;
}

[data-reading-assist] {
  p {
    line-height: var(--line-height);

    [data-assist] {
      --l1: calc(var(--line-height) * var(--assist-lines-first));
      --l2: calc(var(--line-height) * var(--assist-lines-after));
      --in: calc((1 - var(--assist-lines-after) / var(--assist-lines-first) ) * 100%);

      $polygon: null;
      $end: 20;
      @for $i from 0 through $end {
        $polygon:
          $polygon,
          100% calc(var(--l1) + var(--l2) * #{$i}),
          #{if( $i == $end, '', var(--in) calc(var(--l1) + var(--l2) * #{$i}) )};
      }

      --polygon: 0 0, #{$polygon};
      --polygon-left: polygon(var(--polygon), 0 calc(var(--l1) + var(--l2) * #{$end}));
      --polygon-right: polygon(var(--polygon), 100% 0);

      &::before,
      &::after {
        // background: #0080ff33;
        content: "";
        height: var(--p-height, 0);
        width: var(--assist-indent-max);
      }

      &::before {
        // clip-path: var(--polygon-left);
        float: left;
        shape-outside: var(--polygon-left);
      }

      &::after {
        // clip-path: var(--polygon-right);
        float: right;
        shape-outside: var(--polygon-right);
      }
    }
  }
}

// Base style
p {
  margin: var(--line-height) 0;
}

body {
  font-family: Inter, sans-serif;
  margin: auto;
  max-width: 640px;
  padding: 0 var(--line-height);
}
View Compiled
const paragraphs = document.querySelectorAll('[data-reading-assist] p');

const getPHeight = p => {
  let height;
  do {
    height = getComputedStyle(p).height;
    p.style.cssText = `
      ${p.style.cssText};
      --p-height: ${height};
    `;
  } while (parseFloat(height) < parseFloat(getComputedStyle(p).height));
};

// Init reading assit
paragraphs.forEach(p => {
  p.insertAdjacentHTML('afterbegin', '<span data-assist />');
  getPHeight(p);
});

// Support for window resizing
let timeout;
window.addEventListener('resize', () => {
  if (timeout) {
    window.cancelAnimationFrame(timeout);
  }

  timeout = window.requestAnimationFrame(() => {
    paragraphs.forEach(p => {
      getPHeight(p);
    });
  });
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.