画像アップロードの際に、画像の編集をしたいと思い、画像に特化したアップローダを作りました。
新規に構築するサービスで写真をアップロードするような機能を実装する場合、意外と面倒くさい手順が沢山存在します。
そうした手順をライブラリ化しておいたので、便利につかえるモジュールを作ってみました。
汎用性をもたせたつもりですが、機能要望がありましたら、コメントください。
機能一覧
初回版の機能は以下のとおりです。
1. 画像形式のアップロードに特化
拡張子は次の3つのみ、「jpg,png,gif」
2. EXIFライブラリを併用することで、javascriptでのexif情報の取得が可能
https://github.com/exif-js/exif-js
3. 複数画像のアップロードが可能
アップロード前に、画像を確認して、取り消しも可能。
4. スマホの場合に、写真撮影からそのままアップロードすることが可能。
撮影した写真をアップロード前に編集することも可能。
端末に写真を残さないセキュア対応も可能。
ソースコード
1つのディレクトリに以下の2つのソースコードと、「rotate.svg」「delete.svg」をいれて準備します。
;$$fileupload = (function(){
// 起動scriptタグを選択
var __currentScriptTag = (function(){
var scripts = document.getElementsByTagName("script");
return __currentScriptTag = scripts[scripts.length-1].src;
})();
// [共通関数] イベントセット
var __event = function(target, mode, func){
if (target.addEventListener){target.addEventListener(mode, func, false)}
else{target.attachEvent('on' + mode, function(){func.call(target , window.event)})}
};
// [共通関数] URL情報分解
var __urlinfo = function(uri){
uri = (uri) ? uri : location.href;
var data={};
var urls_hash = uri.split("#");
var urls_query = urls_hash[0].split("?");
var sp = urls_query[0].split("/");
var data = {
uri : uri
, url : sp.join("/")
, dir : sp.slice(0 , sp.length-1).join("/") +"/"
, file : sp.pop()
, domain : sp[2]
, protocol : sp[0].replace(":","")
, hash : (urls_hash[1]) ? urls_hash[1] : ""
, query : (urls_query[1])?(function(urls_query){
var data = {};
var sp = urls_query.split("#")[0].split("&");
for(var i=0;i<sp .length;i++){
var kv = sp[i].split("=");
if(!kv[0]){continue}
data[kv[0]]=kv[1];
}
return data;
})(urls_query[1]):[]
};
return data;
};
// [共通関数] DOMの上位検索
var __upperSelector = function(elm , selectors) {
selectors = (typeof selectors === "object") ? selectors : [selectors];
if(!elm || !selectors){return;}
var flg = null;
for(var i=0; i<selectors.length; i++){
for (var cur=elm; cur; cur=cur.parentElement) {
if (cur.matches(selectors[i])) {
flg = true;
break;
}
}
if(flg){
break;
}
}
return cur;
}
// [共通関数] ブラウザのFileAPIが利用できるかどうかチェックする
var __checkFileAPI = function(){
// FileApi確認
if( window.File
&& window.FileReader
&& window.FileList
&& window.Blob) {
return true;
}
else{
return false;
}
};
// [共通関数] JS読み込み時の実行タイミング処理(body読み込み後にJS実行する場合に使用)
var __construct = function(){
switch(document.readyState){
case "complete" : new $$;break;
case "interactive" : __event(window , "DOMContentLoaded" , function(){new $$});break;
default : __event(window , "load" , function(){new $$});break;
}
};
// ----------
// インスタンスベースモジュール(初期設定処理)
var $$ = function(options){
this.replaceOptions(options);
this.options.cacheTime = (+new Date());
if(!this.options.currentPath && __currentScriptTag){
var pathinfo = __urlinfo(__currentScriptTag);
this.options.currentPath = pathinfo.dir;
}
// set-css
this.setCss();
this.setTypeFile();
// upload-button
this.setButton();
};
$$.prototype.options = {
cacheTime : null,
currentPath : null,
css_path : null, // 表示系cssの任意指定(デフォルトは起動スクリプトと同一階層)
file_multi : true, // 複数ファイルアップロード対応 [ true : 複数 , false : 1つのみ]
querys : {}, // input type="hidden"の任意値のセット(cgiに送信する際の各種データ)
btn_selector : "#fileupload", // クリックするボタンのselectors(複数対応)
view_bg_class : "fileUpload-image-bg", // 画像編集用モードのBG(base)要素のclass名
extensions : ["jpg","jpeg","png","gif","svg"], // 指定拡張子一覧
img_rotate_button : null, // 画像編集の回転機能アイコン(デフォルトは起動スクリプトと同一階層)
img_delete_button : null, // 画像編集の削除機能アイコン(デフォルトは起動スクリプトと同一階層)
file_select : function(e){console.log(e)} // submit直前の任意イベント処理
};
// [初期設定] インスタンス引数を基本設定(options)と入れ替える
$$.prototype.replaceOptions = function(options){
for(var i in options){
this.options[i] = options[i];
}
return this.options;
};
// [初期設定] 基本CSSセット
$$.prototype.setCss = function(){
var head = document.getElementsByTagName("head");
if(!head){return;}
var css = document.createElement("link");
css.rel = "stylesheet";
css.href = (this.options.css_path !== null) ? this.options.css_path : this.options.currentPath + "images.css";
head[0].appendChild(css);
};
$$.prototype.getBase = function(){
var lists = document.getElementsByClassName(this.options.view_bg_class);
if(lists.length){
return lists[0];
}
else{
return null;
}
};
// 処理用iframe内のform内のtype=fileを取得
$$.prototype.getForm_typeFile = function(){
return document.querySelector("input[name='fileupload_"+ this.options.cacheTime +"']");
};
// 編集画面の画像一覧リストの取得
$$.prototype.getEditImageLists = function(){
return document.querySelectorAll("."+this.options.view_bg_class+" ul li.pic");
};
$$.prototype.setTypeFile = function(){
var inp = document.createElement("input");
inp.type = "file";
inp.name = "fileupload_" + this.options.cacheTime;
inp.multiple = (this.options.file_multi) ? "multiple" : "";
inp.style.setProperty("display","none","");
inp.accept = "image/gif,image/jpeg,image/png";
__event(inp , "change" , (function(e){
if(typeof this.options.file_select === "function" && __checkFileAPI()){
var input = e.currentTarget;
this.viewImageEdit(input);
}
}).bind(this));
document.body.appendChild(inp);
};
// [初期設定] データ送信ボタンのsubmit処理設定(複数対応)
$$.prototype.setButton = function(){
var btns = document.querySelectorAll(this.options.btn_selector);
for(var i=0; i<btns.length; i++){
__event(btns[i] , "click" , (function(e){this.clickFileButton(e)}).bind(this));
}
};
// データ送信submitボタンクリック時の処理
$$.prototype.clickFileButton = function(e){
var typeFile = this.getForm_typeFile();
typeFile.click();
};
// postデータの拡張子確認
$$.prototype.checkExtension = function(filename){
};
// [画像編集] 送信前の画像編集操作処理
$$.prototype.viewImageEdit = function(targetInputForm){
this.viewBG();
this.viewImages(targetInputForm);
};
// [画像編集] 画像回転処理
// [画像編集] 編集画面表示(複数画像対応)
$$.prototype.viewImages = function(filesElement){
if(!filesElement){return;}
var files = filesElement.files;
if(!files || !files.length){return;}
var bgs = document.getElementsByClassName(this.options.view_bg_class);
if(!bgs || !bgs.length){return;}
var bg = bgs[0];
var ul = document.createElement("ul");
bg.appendChild(ul);
for(var i=0; i<files.length; i++){
var li = document.createElement("li");
li.className = "pic";
li.setAttribute("data-num" , i);
ul.appendChild(li);
var path = URL.createObjectURL(files[i]);
var img = new Image();
// var img = document.createElement("img");
img.src = path;
img.className = "picture";
img.setAttribute("data-num" , i);
__event(img , "load" , (function(e){this.loadedImage(e)}).bind(this));
li.appendChild(img);
var num = document.createElement("div");
num.className = "num";
li.appendChild(num);
var control = document.createElement("div");
control.className = "control";
control.setAttribute("data-num" , i);
li.appendChild(control);
var rotateImage = new Image();
rotateImage.className = "rotate";
rotateImage.src = (this.options.img_rotate_button !== null) ? this.options.img_rotate_button : this.options.currentPath + "rotate.svg";
control.appendChild(rotateImage);
__event(rotateImage , "click" , (function(e){this.clickRotateButton(e)}).bind(this));
var delImage = new Image();
delImage.className = "delete";
delImage.src = (this.options.img_delete_button !== null) ? this.options.img_delete_button : this.options.currentPath + "delete.svg";
control.appendChild(delImage);
__event(delImage , "click" , (function(e){this.clickDeleteButton(e)}).bind(this));
}
var li = document.createElement("li");
li.className = "submit";
ul.appendChild(li);
var sendButton = document.createElement("button");
sendButton.innerHTML = "送信";
__event(sendButton , "click" , (function(e){this.clickSendButton(e)}).bind(this));
li.appendChild(sendButton);
var cancelButton = document.createElement("button");
cancelButton.innerHTML = "キャンセル";
__event(cancelButton , "click" , (function(e){this.clickCancel(e)}).bind(this));
li.appendChild(cancelButton);
};
// [画像編集] BG表示
$$.prototype.viewBG = function(){
var bg = document.createElement("div");
bg.className = this.options.view_bg_class;
document.body.appendChild(bg);
};
// [画像編集] rotateボタンを押した時の処理(左に90度回転)
$$.prototype.clickRotateButton = function(e){
var target = e.currentTarget;
// console.log(target.parentNode.getAttribute("data-num"));
var num = target.parentNode.getAttribute("data-num");
if(num === null){return;}
var targetImage = document.querySelector("."+this.options.view_bg_class+" ul li.pic[data-num='"+num+"'] img.picture");
if(!targetImage){return;}
var rotateNum = targetImage.getAttribute("data-rotate");
rotateNum = (rotateNum) ? rotateNum : "0";
// 反時計回りに回転
switch(rotateNum){
case "0":
rotateNum = 270;
break;
case "90":
rotateNum = 0;
break;
case "180":
rotateNum = 90;
break;
case "270":
rotateNum = 180;
break;
}
targetImage.setAttribute("data-rotate" , rotateNum);
};
//
$$.prototype.clickDeleteButton = function(e){
if(!confirm("アップロードリストから写真を破棄しますか?※直接撮影された写真は保存されません。")){return;}
var target = e.currentTarget;
// console.log(target.parentNode.getAttribute("data-num"));
var num = target.parentNode.getAttribute("data-num");
if(num === null){return;}
var targetListBase = document.querySelector("."+this.options.view_bg_class+" ul li.pic[data-num='"+num+"']");
if(!targetListBase){return;}
targetListBase.parentNode.removeChild(targetListBase);
// ラスト1つを削除した場合は、キャンセル扱い
var lists = this.getEditImageLists();
if(!lists || !lists.length){
this.clickCancel();
}
}
//
$$.prototype.clickCancel = function(){
var base = this.getBase();
if(base){
base.parentNode.removeChild(base);
}
var input = this.getForm_typeFile();
input.value = "";
};
// 画像を読み込んだ際のイベント処理
$$.prototype.loadedImage = function(e){
var img = e.currentTarget;
var num = img.getAttribute("data-num");
if(typeof window.EXIF !== "undefined"){
var res = EXIF.getData(img , (function(img,e) {
var exifData = EXIF.getAllTags(img);
img.setAttribute("data-exif" , JSON.stringify(exifData));
}).bind(this , img));
}
};
$$.prototype.clickSendButton = function(e){
var files = this.getForm_typeFile().files;
var lists = this.getEditImageLists();
for(var i=0; i<lists.length; i++){
var num = lists[i].getAttribute("data-num");
// this.postFile(files[num]);
this.postFiles_cache.push(files[num]);
}
if(this.postFiles_cache.length > 0){
this.postFile(lists[0]);
}
};
$$.prototype.postFiles_cache = [];
$$.prototype.postFile = function(viewListElement){
if(!window.FormData){
console.log("データ送信機能がブラウザに対応していません。");
return;
}
if(!window.XMLHttpRequest){
console.log("AJAX機能がブラウザに対応していません。");
return;
}
// 全て送信完了したら編集画面を閉じる
if(!this.postFiles_cache.length){
this.clickCancel();
return;
}
var fd = new FormData();
if(this.options.querys){
for(var i in this.options.querys){
fd.append(i , this.options.querys[i]);
}
}
fd.append("imageFile" , this.postFiles_cache[0]);
fd.append("info[name]" , this.postFiles_cache[0].name);
fd.append("info[size]" , this.postFiles_cache[0].size);
fd.append("info[type]" , this.postFiles_cache[0].type);
fd.append("info[modi]" , this.postFiles_cache[0].lastModified);
fd.append("info[date]" , this.postFiles_cache[0].lastModifiedDate);
var img = viewListElement.querySelector(".picture");
var rotate = (img.getAttribute("data-rotate")) ? img.getAttribute("data-rotate") : "";
fd.append("info[rotate]" , rotate);
var lists = this.getEditImageLists();
if(!lists.length){return;}
var img = lists[0].querySelector("img");
var exifData = img.getAttribute("data-exif");
if(exifData){
fd.append("exif" , exifData);
}
var XHR = new XMLHttpRequest();
XHR.onreadystatechange = (function(XHR,e){
if (XHR.readyState==4 && XHR.status == 200){
console.log(XHR.responseText);
if(this.postFiles_cache.length){
this.postFiles_cache.shift();
}
var lists = this.getEditImageLists();
if(lists.length){
lists[0].parentNode.removeChild(lists[0]);
}
// 送信後の削除処理をした直後のエレメント一覧の取得
var lists = this.getEditImageLists();
if(lists.length){
setTimeout((function(lists,e){this.postFile(lists)}).bind(this,lists[0]) , 1000);
}
else{
this.clickCancel();
}
}
}).bind(this,XHR);
XHR.open('POST', location.href);
XHR.send(fd);
};
return $$;
})();
.fileUpload-image-bg{
position:fixed;
display:block;
top:0;
left:0;
background-color:rgba(0,0,0,0.5);
width:100%;
height:100%;
z-index:1000;
}
.fileUpload-image-bg ul,
.fileUpload-image-bg li{
list-style:none;
padding:0;
margin:0;
border:0;
width:100%;
}
.fileUpload-image-bg ul{
counter-reset:num;
height:100%;
overflow-Y:auto;
padding:40px 0;
}
.fileUpload-image-bg ul li{
/* min-width:300px;
max-width:640px;
width:50%; */
width:300px;
height:300px;
text-align:center;
position:relative;
margin:20px auto;
}
.fileUpload-image-bg ul li.pic{
padding:4px;
border:1px solid white;
background-color:white;
}
.fileUpload-image-bg ul li.pic:before{
counter-increment: num;
content: counter(num);
position:absolute;
top:0;
left:0;
display:inline-block;
width:30px;
height:30px;
font-size:20px;
color:white;
text-shadow:2px 2px 4px black;
z-index:100;
}
/* .fileUpload-image-bg li .num{
position:absolute;
top:0;
left:0;
display:inline-block;
width:30px;
height:30px;
font-size:20px;
color:white;
text-shadow:2px 2px 4px black;
} */
.fileUpload-image-bg li .control{
position:absolute;
top:calc(50% - 20px);
width:100%;
height:40px;
/* text-align:center; */
/* display: -webkit-flex;
display: flex; */
display:none;
-webkit-justify-content: center;
justify-content: center;
-webkit-align-items: center;
align-items: center;
}
.fileUpload-image-bg li:hover .control{
display: -webkit-flex;
display: flex;
}
.fileUpload-image-bg li .control .rotate,
.fileUpload-image-bg li .control .delete{
width:40px;
height:40px;
cursor:pointer;
filter: drop-shadow(2px 2px 2px black);
/* display:list-item; */
margin:0 20px;
}
.fileUpload-image-bg li .control .rotate:hover,
.fileUpload-image-bg li .control .delete:hover{
opacity:0.5;
}
.fileUpload-image-bg img.picture{
width:100%;
height:100%;
display:block;
margin:auto;
vertical-align:middle;
object-fit:contain;
}
.fileUpload-image-bg img.picture[data-rotate="90"]{
transform:rotate(90deg);
}
.fileUpload-image-bg img.picture[data-rotate="180"]{
transform:rotate(180deg);
}
.fileUpload-image-bg img.picture[data-rotate="270"]{
transform:rotate(270deg);
}
.fileUpload-image-bg li.submit button{
margin:10px 20px;
}
実装方法
1. モジュールの読み込み
画像読み込みを行いたいHTMLファイルでモジュールの読み込みを行います。
<script src="lib/exif.js"></script>
<script src="fileupload/images.js"></script>
2. 画像アップロードボタンを実装
アップロードボタンは、どんなエレメントでも構いません。
<button type="button" id="fileupload">画像アップロード</button>
3. 設定情報を付与してページ内で実行
<script>
new $$fileupload({
form_action : "upload.php",
querys : {
"post1" : "data-1"
},
file_select : function(e){
console.log(e);
}
});
</script>
【解説】
"querys"は、送信時に付与したい情報を追加することができます。
"file_select"は、アップロードを実行して完了後に発生するイベント用コールバック関数です。
受信用PHP
今回は、受信用のPHPコードはサンプルのみ載せておきます。
それぞれのサイトに合わせたコードで記述してお使いください。
<?php
$data = array(
"file" => $_FILES["imageFile"],
"info" => $_POST["info"],
"exif" => $exif
);
print_r($data);
0 件のコメント:
コメントを投稿