LoginSignup
16

More than 5 years have passed since last update.

テキストエリアに画像をドラッグアンドドロップしてプレビュー表示しながら編集出来るUI

Last updated at Posted at 2016-08-27

色々と調べたがまとまっている記事が無かったので、備忘録としてメモ。
まだまだ改善の余地、というかダメな部分も多々あるが、ひとまずそれぞれの仕組みの細かいことは置いておいて、手順だけ。

全体の構成

1.<textarea>は使わずに、<div>を使う。
contenteditable="true"を指定することで中身が編集できるようになるので、これをテキストエリアと見なす。

2.テキストエリアにドロップされた画像をFileReaderで読み込み、<div contenteditable="true">の中に<img>要素を生成してプレビューする。

3.アップロードの際は、<img>要素をFormDataに組み込んでXMLHttpRequest.send()で送信する。

ソース

<form action="upload/path" id="" method="post">
    <div id="alt_textarea" contenteditable="true" style="height: auto; min-height: 250px;">
  </div>
  <input type="submit" id="submit_btn" name="submit">
</form>
/***
  ***********************************************
  ** ドラッグアンドドロップで画像をアップした時の動き **
  ***********************************************
****/
//ブラウザデフォルトのドラッグ&ドロップ機能を無効化
document.addEventListener('dragover',function(event){
  event.preventDefault();
}, false);
document.addEventListener('drop',function(event){
  event.preventDefault();
}, false);
//div要素にドラッグしていった時の挙動
var div = document.getElementById('alt_textarea');
div.addEventListener('dragover', function(){
  div.style.border = '3px Aqua dashed';
}, false);
//ドラッグで離れた時の挙動
div.addEventListener('dragleave',function(){
  div.style.border = '';
}, false);
//ドロップした時の挙動
div.addEventListener('drop', function(event){
  //現在のカーソル位置
  var sel = window.getSelection();
  if (sel.anchorNode === null) {
    alert('テキストエリアにカーソルを合わせてください');
    div.style.border = '';
    return;
  }
  event.preventDefault();
  div.style.border = '';
  //DataTransfer オブジェクト、ファイルリストを取得する
  var files = event.dataTransfer.files;
  if(!files) { return; }
  for(var i = 0; i < files.length;i++){
    //ファイルを取得する
    if (!files[i].type.match('image.*')) {
      alert('画像をアップしてください');
      return;
    }
    var reader = new FileReader();
    //エラー処理
    reader.addEventListener('error', function (event){
      console.log('error' + event.target.error.code);
    }, false);
    //読み込み後の処理
    reader.addEventListener('load', function (event){
      var img = document.createElement('img');
      img.src = event.target.result;
      img.width = 300;
      var range = sel.getRangeAt(0);
      range.insertNode(img);
    }, false);
    reader.readAsDataURL(files[i]);
  }
}, false);

/***
  ***********************************************
  ** submitボタンを押された時の挙動
  ***********************************************
****/
var form = document.forms[0];
form.addEventListener('submit', function(event) {
  event.preventDefault();
  this.submit.disabled = true;
  var defaultValue = this.submit.value;
  this.submit.value = '送信中';
  var div = document.getElementById('alt_textarea');
  var div_tmp = document.createElement('div');
  div_tmp.innerHTML = div.innerHTML;
  var images = Array.prototype.slice.call(div_tmp.getElementsByTagName('img'));
  var form_data = new FormData(this);
  for (var i = 0; i < images.length; i++) {
    var altImage = document.createTextNode("articleImage_" + i);
    images[i].parentNode.insertBefore(altImage,images[i]);
    images[i].parentNode.removeChild(images[i]);
    form_data.append('eyecatch[]',images[i].src);
  };
  var xhr = new XMLHttpRequest();
  form_data.append('body',div_tmp.innerHTML);
  //エラー処理
  xhr.addEventListener('error', function (e){
    console.log('error' + e.target.error.code);
  },false);
  xhr.addEventListener('load', function (e){
    if (this.status == 200) {
      var url = 'redirect/url';
      window.location.href = url;
    } else {
      form.submit.disabled = false;
      form.submit.value = defaultValue;
      console.log('入力に間違いがあります。');
    }
  }, false);
  xhr.open("POST" , this.getAttribute('action'));
  xhr.send(form_data);
}, false);

補足1

  for (var i = 0; i < images.length; i++) {
    var altImage = document.createTextNode("articleImage_" + i);
    images[i].parentNode.insertBefore(altImage,images[i]);
    images[i].parentNode.removeChild(images[i]);
    form_data.append('eyecatch[]',images[i].src);
  };

FormDataに画像を追加しつつ、テキストエリアの文字列を置き換えているのは、アップされた記事を後に表示する際にサーバーサイドで処理しやすくするためである。

  articleImage_1 => <img src="hogehoge1.jpg">
  articleImage_2 => <img src="hogehoge2.jpg">

表示する際は、記事の内容中の文字列を上記のように該当の<img>で置き換え、<?php>echo htmlspecialchars_decode()<?>とかで出力すれば画像が途中に埋め込まれた形で表示出来る。

何かもっと賢い方法が見つかったら変更予定。

補足2

var images = Array.prototype.slice.call(div_tmp.getElementsByTagName('img'));

にてArray.prototype.slice.call()を使っているのは、取得した要素の追従をなくすため。
以前書いた記事ykztsさんに教えていただきました。ありがとうございます。

ここで追従を無くさないと、補足1で書いた操作をした時に<img>要素そのものが消えてしまい、FormDataに追加できなくなってしまう。

まとめ

ひとまずこれでそれっぽい動きはするようになった。

Laravelを使っているのでバリデーションはサーバーサイドでやりたいし、Flash Messageも使いたいのでできればAjaxでのsubmitではなく、もっと静的に送信したいが何か方法あるのかな?

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16