見出し画像

【同人むけ】チャットノベルを個人サイトに実装する【htmlとCSSとJavaScript】

どもです。前回は濁点喘ぎを縦書き対応させることに心血を注いていた筆者ですが、今回はチャットノベルを自家サイトに置けるようにします。

チャットノベルは2018年ぐらい頃??からWebやアプリによる投稿サイトが登場しておりますが、個人サイト用のテンプレを見た覚えがなく…。多くはテキストのみの脚本体裁か、ゲームや動画におさめるフォーマットで公開されているんではないでしょうか。

以下、作ってみた覚書です。なにかあれば更新します。


サンプル一式

全体の説明が長くなりそうなので、めんどい人は下記一式持って帰っていじってなんとかしてください。HTMLファイル1つとやっつけ画像ファイルが6つ入ってます。

セリフが画面の途中からファーッと出るようにする

チャットノベルは動的な見せ方が特徴的なメディアですが、今回は画面タップでのアクション類は使わずPC閲覧むけに寄せることにします。JavaScript知識がよわよわだったためChatGPTにガン詰めお願いしてプログラムを組んでもらいました。都合のいい時ばっかりAIを使う…。

JavaScript記述例

window.addEventListener('scroll', function() {
    const elements = document.querySelectorAll('.fade, .another-fade, .additional-fade-1, .additional-fade-2, .additional-fade-3');
    const window_height = window.innerHeight;

    for (let i = 0; i < elements.length; i++) {
        const element_height = elements[i].getBoundingClientRect().bottom;

        if (element_height < window_height * 0.8) {
            elements[i].classList.add('active');
        } 
    }
});

JavaScriptは上記だけです。ここでは「特定のクラス指定したタグ要素が画面全体の上から80%の高さに入ってきた段階で、指定クラスにactiveを追加する」という動きを自動化します。クラスとかactiveっていうのはCSS(カスケーティングスタイルシート)の話です。

で、これを織り込んだHTMLとCSSを組みます。

HTML記述例

※実装時にはpタグ内要素のインデント(タブ・半角スペース等)は全てトルツメしてください。フキダシの中身が膨張して見栄えが死にます。

<div class="fade">
  <p class="fukidashi">
    <span class="name kawata">川田</span>
    <span class="line">あ、こんにちは</span>
  </p>
</div>
<div class="another-fade">
  <p class="fukidashi">
    <span class="name yasu">ヤス</span>
    <span class="line">こんにちはー</span>
   </p>
</div>
<div class="additional-fade-1">
  <p class="move w1">
    ぽいーん!
  </p>
</div>

CSS記述例


/* .fadeの挙動スタイル */
/* 左から右へ出てくる。ゲスト全般 */
.fade {
  /* 追加のスタイルをここに記述 */
	opacity: 0;
	transform: translateX(-15px);
	transition: opacity 1s, transform 0.5s;
}
.fade.active {
  /* 追加のアクティブ時のスタイルをここに記述 */
  	opacity: 1;
	transform: translateX(0px);
}

/* .another-fadeの挙動スタイル */
/* .右から左へ出てくる。主人公 */
.another-fade {
  /* 追加のスタイルをここに記述 */
	opacity: 0;
	transform: translateX(15px);
	transition: opacity 1s, transform 0.5s;
}
.another-fade.active {
  /* 追加のアクティブ時のスタイルをここに記述 */
  	opacity: 1;
	transform: translateX(0px);
}

/* 追加のクラス1の挙動スタイル */
/* 下から上へ出てくる。効果音やト書きなどポップアップ */
.additional-fade-1 {
  /* 追加のスタイルをここに記述 */
	opacity: 0;
	transform: translateY(50px);
	transition: opacity 1s, transform 0.5s;
}
.additional-fade-1.active {
  /* 追加のアクティブ時のスタイルをここに記述 */
  	opacity: 1;
	transform: translatey(0px);
}

元のJavaScriptでは挙動を5種類まで付けられるよう.fade, .another-fade, .additional-fade-1, .additional-fade-2, .additional-fade-3といった専用クラスを5個作っています。多分このへんのクラス名称・個数は任意で変えられると思うんですが、今回はChatGPTの提案そのままです。

CSSでは、「要素にactiveがつくまでは透明度100%でちょっとズレた場所に隠れていて、要素が上がってきてクラス付与されると透明度0%へ切り替えつつズイッと出てくるよ」という動作をしています。opacityの1sって部分が透明度の変化にかかる時間、transformの0.5sって部分が要素の移動にかかる時間です。「1秒かけて透明度を変化させながら0.5秒で要素がスライドしますよ」ということですね。

チャットシーンとリアルシーンを大枠で設定する

作品でいう場面転換にあたる箇所です。これらセリフ・ト書きを、場面設定を指定するdiv枠で囲みます。チャットノベルでは主に2種類のフォーマットを使い分け、
(1)LINEやSMSなどに似せたチャット風場面
(2)リアルでキャラクター同士が会って喋っている設定の場面
を組み合わせていきます。

【HTML】
<div class="chat">
	<div class="fade">
		<p class="fukidashi"> <span class="name kawata">川田</span> <span class="line">あ、こんにちは</span> </p>
	</div>
	<div class="another-fade">
		<p class="fukidashi"> <span class="name yasu">ヤス</span> <span class="line">こんにちはー</span> </p>
	</div>
	<div class="additional-fade-1">
		<p class="move w1"> ぽいーん! </p>
	</div>
</div>

<div class="real">
	<div class="fade">
		<p class="fukidashi"> <span class="name kawata">川田</span> <span class="line">あ、こんにちは</span> </p>
	</div>
	<div class="another-fade">
		<p class="fukidashi"> <span class="name yasu">ヤス</span> <span class="line">こんにちはー</span> </p>
	</div>
	<div class="additional-fade-1">
		<p class="move w1"> ぽいーん! </p>
	</div>
</div>

div class="chat"とdiv class="real"が外枠に増えました。

アイコンとフキダシを成形する

ネーミングについてはあまり気にせずに

で、この段階ではまだスクロールで出現するのがシンプルなテキストのみになっています。これにCSSを使ってアイコンをつけ、セリフ箇所をフキダシっぽく整形します。

CSS/* 名前欄のスタイル */
.name{
font-weight:bold;
color:#fff;
font-size: .8rem;
position: absolute;
top: -25px;
left: 50px;
}

/* 顔アイコン(左配置時) */
.name::before {
content: url("1x1.png");
vertical-align: middle;
padding-right: 5px;
width:40px;
height:45px;
border-radius: 50%;
background-size: cover;
background-repeat: no-repeat;
background-position: center;
position: absolute;
top: -5px;
left: -50px;
}

/* 顔アイコンの個別スタイル設定 */
span.kawata::before,span.kawata::after {
background-image: url("kawata.png");
}
span.yasu::before,span.yasu::after {
background-image: url("yasu.png");
}

/* 吹き出しのスタイル */
.line{
max-width:274px;
display: inline-block;
}
.fukidashi{
border-radius: 10px;
padding: 10px 16px;
margin:0 0 0 30px;
font-size: .95rem;
letter-spacing:0.5;
white-space: break-spaces;
word-break: break-all;
line-height:1.4;
z-index: 0;
display:inline-block;
}

上記スタイルは共用部分になります。ここから、チャット時/リアル時、主人公(セリフ右配置)/ゲスト(セリフ左配置)ごとのスタイルをカスタムしていきます。さっき大枠にchat枠とreal枠を作ったのでシチュ別の細かい指定ができるようになりました。

/* 吹き出しの個別スタイル */
/* リアル-主人公 */
div.real .another-fade {
margin-top:1.5rem;
padding-bottom: 20px;
display:flex;
justify-content: flex-end;
}
div.real .another-fade .fukidashi{
margin:0 30px 0 0;
}
div.real .another-fade .name{
display:block;
top: -25px;
right: 47px;
text-align: right;
}
div.real .another-fade .name::before {
content: none;
}
div.real .another-fade .name::after {
content: url("1x1.png");
vertical-align: middle;
padding-right: 5px;
width:40px;
height:45px;
border-radius: 50%;
background-size: cover;
background-repeat: no-repeat;
background-position: center;
position: absolute;
top: -5px;
right: -47px;
}
div.real .another-fade p{
background-color:#fff;
}

/* チャット-主人公 */
div.chat .another-fade {
margin-top:1.5rem;
padding-bottom: 20px;
display:flex;
justify-content: flex-end;
}
div.chat .another-fade .fukidashi{
margin:0 50px 0 0;
border-radius: 20px;
}
div.chat .another-fade .fukidashi:after{
content: "";
position: absolute;
width: 0;
height: 0;
right: 46px;
top: 0px;
border-style: solid;
border-color: transparent transparent transparent rgb(140, 224, 47);
border-width: 10px 0 10px 11.32px;
transform: rotate(84deg);
}
div.chat .another-fade .name{
display:block;
top: -25px;
right: 47px;
text-align: right;
}
div.chat .another-fade .name::before {
content: none;
}
div.chat .another-fade .name::after {
content: url("1x1.png");
vertical-align: middle;
padding-right: 5px;
width:40px;
height:45px;
border-radius: 50%;
background-size: cover;
background-repeat: no-repeat;
background-position: center;
position: absolute;
top: -5px;
right: -47px;
}
div.chat .another-fade p{
background-color:rgb(140, 224, 47);
}

/* リアル-ゲスト */
div.real .fade {
margin-top:1.5rem;
padding-bottom: 20px;
position: relative;
}
div.real .fade p{
background-color:#fff;
}

/* チャット-ゲスト */
div.chat .fade {
margin-top:1.5rem;
padding-bottom: 20px;
position: relative;
}
div.chat div.fade .fukidashi {
margin:0 0 0 50px;
border-radius: 20px;
}
div.chat div.fade .fukidashi:before{
content: "";
position: absolute;
width: 0;
height: 0;
left: 46px;
top: 0px;
border-style: solid;
border-color: transparent transparent transparent #fff;
border-width: 10px 0 10px 11.32px;
transform: rotate(95deg);
}
div.chat div.fade p{
background-color:#fff;
}

補講1:Webフォントと絵文字を使って通話アイコンの代用にする

ヤスの話が長い

また、チャットノベルでは通話があったことを示す特殊アイコンなどがあります。画像を使ってもいいんですが、今回はWebフォントNoto Emojiを導入し絵文字を使って代用してみることにしました。アイコンを使うところのfukidashiクラス部分に「action」クラスを追加します。

【HTML】
■HEADタグ内に追加
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Emoji:wght@300..700&display=swap" rel="stylesheet">

■BODYタグ内に追加
<div class="chat">
	<div class="fade">
<p class="fukidashi action"><span class="name kawata">川田</span><span class="line emoji">📞</span><br><span class="line under_emo1">着信</span></p>
	</div>
	<div class="another-fade">
<p class="fukidashi action"><span class="name yasu">ヤス</span><span class="line emoji">📞</span><br><span class="line under_emo1">通話終了</span><br><span class="line under_emo2">1:32:45</span></p>
	</div>
</div>
CSS/* 通話・SNSアイコン風 */
.action{
line-height: 1.2;
white-space: pre-wrap;
text-align:center;
font-style: normal;
font-weight: 700;
min-width:70px;
}
.emoji {
font-family: "Noto Emoji", sans-serif;
font-optical-sizing: none;
font-size: 2rem;
}
.under_emo1{
font-family: sans-serif;
font-size: .7rem;
}
.under_emo2{
font-family: sans-serif;
font-size: .7rem;
}

/* リアル-主人公-通話アイコン */
div.real .another-fade .action{
margin:0 50px 0 0;
border-radius: 20px;
}
div.real .another-fade .action:after{
content: "";
position: absolute;
width: 0;
height: 0;
right: 46px;
top: 0px;
border-style: solid;
border-color: transparent transparent transparent #fff;
border-width: 10px 0 10px 11.32px;
transform: rotate(84deg);
}

/* リアル-ゲスト-通話アイコン */
div.real div.fade .action {
margin:0 0 0 50px;
border-radius: 20px;
}
div.real div.fade .action:before{
content: "";
position: absolute;
width: 0;
height: 0;
left: 46px;
top: 0px;
border-style: solid;
border-color: transparent transparent transparent #fff;
border-width: 10px 0 10px 11.32px;
transform: rotate(95deg);
}

補講2:地の文・ト書き・擬音のポップアップ

躍動感

セリフ以外の要素をポンと出すブロックを作ってみましょう。pタグにクラスを付与して細かい見映えを変えます。

【HTML】
<div class="additional-fade-1">
	<p class="move w1">ぽいーん!</p>
</div>
<div class="additional-fade-1">
	<p class="move w2">ぽいーん!</p>
</div>
<div class="additional-fade-1">
	<p class="move b1">ぽいーん!</p>
</div>
<div class="additional-fade-1">
	<p class="move b2">ぽいーん!</p>
</div>
CSS/* ポップアップ基本 */
.move {
margin: 1.5rem 0 4rem;
padding-bottom: 20px;
padding: 16px;
line-height: 1.5;
white-space: pre-wrap;
text-align:center;
}
/* 下地なし 白字 */
.w1{
color:#fff;
}
/* 下地黒 白字 */
.w2 {
color: rgb(250, 250, 250, 1);
background-color: rgb(49, 50, 56, 0.8);
border-radius: 8px;
}
/* 下地なし 黒字 */
.b1{
color:#000;
}
/* 下地白 黒字 */
.b2{
color: rgb(0, 0, 0, 1);
background-color: rgb(255, 255, 255, 0.8);
border-radius: 8px;
}

補講3:通知バナー風のものがポンと出るとそれっぽい

スマホをいじってて新着があったときの通知バナーも作ってみましょう。 要素の挙動を制御する枠(fadeとかanother-fade枠)と同じ階層でこんな枠をつくります。

【HTML】
<div class="additional-fade-1">
	<p class="move sns"><span class="snstitle">SNSバナー</span><span class="snssub">こんな感じに出ます。コード上の隙間はトルツメしてください</span></p>
</div>

<div class="fade"> (省略) </div>
<div class="another-fade"> (省略) </div>
CSS/* SNSバナー風 */
.sns{
background-color: rgb(255, 255, 255, 1);
background-image: url("sns.png");
background-size: 25px;
background-repeat: no-repeat;
background-position: 7px 7px;
border-radius: 8px;
padding: 9px 10px;
text-align:left;
margin-bottom: 4rem;
font-family: sans-serif;
vertical-align: top;
}
.snstitle{
/* SNSバナータイトル部分 */
display:block;
padding-left:27px;
font-size: .9rem;
}
.snssub{
/* SNSバナー本文部分 */
display:block;
font-size: .9rem;
padding: 7px 0px;
color:#888;
}

レイアウト用の大枠で囲む

このままですと画面の幅いっぱいにチャットノベルが広がるので、幅を締める枠とクラスをつくります。今回は650px幅にします。

【HTML】
<div class="wrapper parallax-sticky">
	<div class="chat"> (省略) </div>
	<div class="real"> (省略) </div>
</div>
CSS/* ギミック以外の外枠組み設定 */
body{
margin:0;
padding:0;
}
.wrapper{
width:650px;
margin:auto;
padding:0;
font-family: sans-serif;
}

/* ※画面幅が650pxより小さいとき */
@media screen and (max-width: 650px) {
.wrapper{
width:100%;
margin:0;
}
}

スマートフォン等、スクリーン幅が650px以下になりうる環境ではコンテンツが幅いっぱいになるようCSSのmedia指定で分岐をいれました。

iPhoneで見栄えが死ぬバグに対応しつつ、シーン別背景を組み込む

ここまで来たら本文は大体見れるようになっていると思うのですが、リアルシーン用の背景画像がまだですね。入れましょう。チャットノベルは背景画像を固定にして本文がスルスル動くとそれっぽくなります。定番CSSとしてbackground-attachment:fixed;属性があるのですが、iPhoneではこいつが死ぬため回避策が必要です。

今回はposition:sticky;ですとかoverflow:clip;を使っています。詳細はぐぐっていただいて。HTMLではシーン設定枠(chatかrealが設定されてる枠)の中の最後のとこに背景下敷き要素をいれます。

【HTML】
<div class="wrapper parallax-sticky">

	<div class="real">
		<div class="fade">
			<p class="fukidashi"> <span class="name kawata">川田</span> <span class="line">あ、こんにちは</span> </p>
		</div>
		<div class="another-fade">
			<p class="fukidashi"> <span class="name yasu">ヤス</span> <span class="line">こんにちはー</span> </p>
		</div>
		<!-- 背景用要素 -->
		<div class="parallax-bg">
			<div class="bg bg1"></div>
		</div>
	</div>

	<div class="real">
		<div class="fade">
			<p class="fukidashi"> <span class="name kawata">川田</span> <span class="line">こんにちは2</span> </p>
		</div>
		<!-- 背景用要素 -->
		<div class="parallax-bg">
			<div class="bg bg2"></div>
		</div>
	</div>

</div>
CSS/* 以下、背景用処理 */
.wrapper .real {
position: relative;
z-index: 1;
}
.wrapper .parallax-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
overflow:clip;
}
.wrapper .parallax-bg .bg {
background: no-repeat center center/cover;
width: 100%;
height: 100vh;
position: sticky;
top: 0;
z-index: -1;
}
/* 個別背景の画像指定 */
.wrapper .parallax-bg .bg1 {
background-image: url("bg1.png");
}
.wrapper .parallax-bg .bg2 {
background-image: url("bg2.png");
}

セレクタ部分、「.wrapper .parallax-bg .bg1」と階層を重ねて指定しないとすぐ死にます。.bg1だけでええやんとか思わずに丁寧にいきましょう。

完成

わあい!できたね!

できました!(※成果物は冒頭のサンプル参照)
あとは自創作用のアイコンと背景画像を調達し、テキストエディタの置換などを活用してチャットノベル本文を流し込むだけです! ありがとうございました。

ちなみに脚本形式の作品や会話劇、TRPGや人狼ログなどにも応用がきくテンプレなのではないかと思います。みなさんもよいチャットノベルライフを。


この記事が気に入ったらサポートをしてみませんか?