💡

モーダルウィンドウ外背景を選択して、それを閉じる機能

2023/10/15に公開

現在作成している画像投稿サイトの最終成果物について
タイトルの機能を実装しようとしたら、苦労したので
過去の自分のように同じ悩みで困っている方への助けになれば。

解決策

モーダルウィンドウ外の背景として
<div class="modal_overlay modal_close"></div>
を追記する。jquery側で上記要素に対するクリックを監視し、
クリックイベントでキャンセルの挙動を発火させる。

詳しく

html

htmlと実際のイメージについては下記になります。
※cropper.jsを使用しているため、記載のhtmlと画像のhtmlに差異があります。

<!-- モーダルウィンドウ -->
<div id="uploadModal" class="modal">
	<div class="modal_overlay modal_close"></div> <-この1行を追加
	<div class="modal_box">
 		<h2 class="modal_head_title">画像トリミング</h2>
    		<div class="preimage_container">
    			<img id="upImagePreview" class="up_image_preview" src="#" alt="プレビュー画像" >
       	</div>
       	<div class="button_box">
         	<button id="upCrop">トリミング</button>
         	<button id="upCancel">キャンセル</button>
     	</div>
     </div>
</div>

jquery

class="modal_overlay"をクリックしたら、モーダルウィンドウを
閉じる挙動を記載します。

//トリミング領域外を選択してもキャンセルボタンと同じ挙動を示す
$('.modal_close').click(function (e){
    //ボタンクリック時のデフォルト動作(フォーム送信)を防ぐ
    e.preventDefault();
    // トリミングを破棄
    cropper.destroy();
    
    // フォームのファイルフィールドをクリア
    $('#fileInput').val('');
    //モーダルウィンドウを閉じる
    $('#uploadModal').css('display', 'none');
    //ファイルを選択する、というboxを表示
    $('#input_file_box').css('display', 'flex');
});

全コード

以下に画像に記載しているイメージのコードを記載します。
まだ作成途中ですが、コードをそのまま貼り付けても動作するかと思います。
htmlのheadには下記を追記してください。
※なお、css,jsのpathについてはご自分のものに合わせて変更してください。

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.12/cropper.min.css">
<link rel="stylesheet" type="text/css" media="screen" href="/css/style.css" >
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.12/cropper.min.js"></script>
<script src="https://kit.fontawesome.com/96a28a998b.js" crossorigin="anonymous"></script>
<script src="/js/triming.js"></script>
<script src="/js/form.js"></script>
uploadForm.php

<h2>写真を投稿する</h2>

<form method="post" enctype="multipart/form-data" action="送信先">
	<div class="contentWrapper">
		<div class="contents">
			<div class="Explain">
				写真
				<br><span class="required">※写真のサイズは縦横比 4:3 です</span>
			</div>
			<div class="box" id="input_file_box">
				<label class="input_file_label" onclick="document.getElementById('fileInput').click()">ファイルを選択</label>
			</div>
			<div id="croppedImageContainer">
			  	<img id="upCroppedImage" alt="トリミング結果"  src="#">
				<button id="removeImage">×</button>
			</div>
			<input type="file" id="fileInput" class="hidden-input" name="up_image">
			
			<!-- モーダルウィンドウ -->
			<div id="uploadModal" class="modal">
				<div class="modal_overlay modal_close"></div>
				<div class="modal_box">
		    		<h2 class="modal_head_title">画像トリミング</h2>
		       		<div class="preimage_container">
		       			<img id="upImagePreview" class="up_image_preview" src="#" alt="プレビュー画像" >
			       	</div>
			       	<div class="button_box">
					<button id="upCrop">トリミング</button>
					<button id="upCancel">キャンセル</button>
		        	</div>
		        </div>
			</div>
		</div>
	
		<div class="contents">
			<div class="Explain">説明</div>
			<textarea class="comment_area" name="content" placeholder="説明を入力する" value="<?php echo $content; ?>"></textarea>
		</div>
		<div>
			<button type="submit" name="submit" value="submit">投稿する</button>
		</div>
	</div>

</form>
style.css
/* /post/UploadForm */

.contents{
    display: flex;
    width: 100%;
    margin-bottom: 1.4rem;
}

.Explain {
    color: #431;
    font-size: 1.8rem;
    font-weight: 700;
    line-height: 1.4;
    margin-right: 4rem;
    width: 30rem;
}

.required {
    color: #c0c0c0;
    font-size: 1.2rem;
    font-weight: 300;
    line-height: 1.4;
}

.box{
    background: #f6f5f4;
    border: .1rem dashed #a9a9a9;
    border-radius: .3rem;
    color: #431;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 24rem;
    text-align: center;
    width: 32rem;
}

.input_file_label{
    background: #fff;
    border: .1rem solid #8c8477;
    border-radius: .3rem;
    cursor: pointer;
    display: block;
    font-size: 1.4rem;
    width: 21.8rem;
    margin: auto;
    transition: background 0.5s ease;
}

.input_file_label:hover{
    background: #d3d3d3;
}



.modal {
    display: none;
    position: fixed;
    z-index: 800;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}

.modal_overlay {
    width: 100%;
    height: 100%;
    position: absolute;    
    background-color: rgba(0,0,0,0.7);
}

.modal_box {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    padding: 1rem;
    border-radius: .3rem;
    background-color: #fff;
}


.modal_head_title {

}

.preimage_container {
    width: 32rem;
    height: 24rem;
    margin-bottom: 1rem;
}

.modal_content {
    background-color: #fefefe;
    border: 1px solid #888;
    width: 32rem;
    height: 24rem;
}

.cropper-view-box {
    outline: 3px dashed #fff;
    outline-color: #fff;
}

.cropper-line,
.cropper-point {
    background-color: #fff;
}



.button_box {
    display: flex;
    justify-content: space-around;
}

#fileInput.hidden-input {
  display: none;
}

/* プレビュー画像のスタイル */
#croppedImageContainer {
    display: none;
    height: 24rem;
    padding: 0;
    position: relative; 
    width: 32rem;
}

#upCroppedImage{
    display: none;
    height: 24rem;
    width: 32rem;
}


/* × ボタンのスタイル */
#removeImage {
    display: none;
    position: absolute; /* 親要素に対して絶対的な位置を設定 */
    top: 0; /* 上からの距離(調整が必要かもしれません) */
    right: 100; /* 右からの距離(調整が必要かもしれません) */
}

.comment_area{
    background-color: #f6f5f4;
    border: .1rem solid #cfcabf;
    border-radius: .8rem;
    font-size: 1.4rem;
    padding: 1rem;
    line-height: 1.4;
    outline: 0;
    transition: background-color 0.3s ease;
    width: 32rem;
    resize: none;
}

.comment_area.active {
    background-color: white; /* 背景色を白に変更 */
    pointer-events: auto; /* 入力を有効にする */
}
triming.js
$(function () {
    let cropper;
    
    // ファイル選択時の処理
    $('#fileInput').change(function () {
 

        // 前回の Cropper インスタンスが存在する場合、破棄する
        if (cropper) {
            cropper.destroy();
        }

        let file = this.files[0];

        if (file) {

            //フォームからアップロードした画像を非同期で読み込むためにFileReaderオブジェクトを作成
            let reader = new FileReader();

            reader.onload = function (e) {
                // 画像をプレビューとして表示
                $('#upImagePreview').attr('src', e.target.result);
                $('#uploadModal').css('display', 'block');
                
                // Cropperインスタンスを初期化
                cropper = new Cropper(document.getElementById('upImagePreview'), {
                    aspectRatio: 4 / 3,
                    viewMode: 2,
                    guides: false,
                    autoCropArea: 1,
                });
            };
            reader.readAsDataURL(file);
        }
    });

    // トリミングボタンのクリックイベント
    $('#upCrop').click(function (e) {

        // ボタンクリック時のデフォルト動作(フォーム送信)を防ぐ
        e.preventDefault(); 
        
        // 現在の切り抜き画像の情報を取得
        let croppedCanvas = cropper.getCroppedCanvas();

        //切り抜き画像のDataURLを取得
        let croppedData = croppedCanvas.toDataURL('image/jpeg');

        //切り抜き画像からBlobを生成し、
        croppedCanvas.toBlob(function (imgBlob){

            //jsにおいてファイルを扱うためにFileオブジェクトを作成
            const croppedImgFile = new File([imgBlob], 'trimimage.jpeg', {type: "image/jpeg"});

            //フォームに新しいファイルデータを設定するためにDataTransferオブジェクトを作成
            const dt = new DataTransfer();

            dt.items.add(croppedImgFile);
            document.querySelector('input[name="up_image"]').files = dt.files;
        });

        //ファイルを選択する、というboxを非表示
        $('#input_file_box').css('display', 'none');

        //トリミング結果が格納されたdivを表示
        $('#croppedImageContainer').css('display', 'block');

        // トリミング結果を表示
        $('#upCroppedImage').attr('src', croppedData);
        $('#upCroppedImage').show();

        //×ボタン表示
        $('#removeImage').css('display', 'block');
        
        // モーダルウィンドウを閉じる
        $('#uploadModal').css('display', 'none');

    });

    //キャンセルボタンのクリックイベント
    //前回トリミングしたデータをそのまま再使用する
    $('#upCancel').click(function (e){

        //ボタンクリック時のデフォルト動作(フォーム送信)を防ぐ
        e.preventDefault();

        // トリミングを破棄
        cropper.destroy();
        
        // フォームのファイルフィールドをクリア
        $('#fileInput').val('');

        //モーダルウィンドウを閉じる
        $('#uploadModal').css('display', 'none');

        //ファイルを選択する、というboxを表示
        $('#input_file_box').css('display', 'flex');

    });

    //トリミング領域外を選択してもキャンセルボタンと同じ挙動を示す
    $('.modal_close').click(function (e){

        //ボタンクリック時のデフォルト動作(フォーム送信)を防ぐ
        e.preventDefault();

        // トリミングを破棄
        cropper.destroy();
        
        // フォームのファイルフィールドをクリア
        $('#fileInput').val('');

        //モーダルウィンドウを閉じる
        $('#uploadModal').css('display', 'none');

        //ファイルを選択する、というboxを表示
        $('#input_file_box').css('display', 'flex');

    });




    // プレビュー画像の × ボタンをクリックしたときの処理
    $('#removeImage').click(function (e) {

        //ボタンクリック時のデフォルト動作(フォーム送信)を防ぐ
        e.preventDefault();        
        
        //トリミング結果が格納されたdivを非表示
        $('#croppedImageContainer').css('display', 'none');
        
        //×ボタン非表示
        $('#removeImage').css('display', 'none');
        
        // フォームのファイルフィールドをクリア
        $('#fileInput').val('');

        //ファイルを選択する、というboxを表示
        $('#input_file_box').css('display', 'flex');


    });

});
form.js
$(function() {
	let textarea = $('.comment_area');

	//textareaをクリックしたときの処理
	textarea.focus(function(e){
		e.stopPropagation();
		textarea.addClass('active');
		textarea.removeAttr("placeholder");
	});

	//ドキュメントのどこかをクリックしたときの処理
	$(document).click(function() {
		textarea.removeClass('active');
		textarea.attr("placeholder", "説明を入力する");
	});

	// textarea内のクリックイベントが親要素に伝播しないように設定
	textarea.click(function(e){
		e.stopPropagation();
	});

})

Discussion