php html css javascript
image gallery 이미지 저장소 만들기
작업 기간
- 2024-02-22 ~ 2024-03-06 (총13일)
- 중간에 kernel panic으로 인한 서버 재설치로 시간이 좀 더 지체됨
작업 목록
업로드폼
- 파일선택
- 선택 후 이미지 표시
- 드래그 드랍
- 드래그 드랍 이벤트
- 파일을 form data로 연결
- 복사 붙여넣기
- Ctrl+V 키보드 이벤트
- 클립보드를 form data로 연결
- 파일검사 : 이미지, 용량, 확장자 등
이미지 파일
- 이미지를 업로드 받고 폴더에 정리
- 파일명 규칙
- 이미지 크롭
- 원하는 위치 크기 크롭을 넣으면 구글포토를 한번 거치지 않아도 될것 같은데..
- cropper.js 사용
- 이미지 크기변경
보안
- 이미지 업로드, 삭제 제한
- 구글로그인 같은거 사용하면 좋을텐데
- 패스워드 암호화
- 동일 도메인의 form 데이터만 받기 - 세션으로 해결 가능할것 같다.
- HTTPS용 인증서 발급
- Let’s Encrypt
- 인증서 자동갱신
해상도 규칙
- 해상도 참고 : https://namu.wiki/w/%ED%95%B4%EC%83%81%EB%8F%84/%EB%AA%A9%EB%A1%9D
- 320*180 : 16:9
- qVGA, 240*160, 3:2
- VGA, 640*480, 4:3
- WSVGA, 1024*576, 16:9
- HD, 1280*720, 16:9
- FHD, 1920*1080, 16:9
- QHD, 2560*1440, 16:9
- UHD, 3840*2160, 16:9
해상도 목표
- 취급 비율은 16:9
- 취급해상도는 320180, WSVGA(1024576), HD(1280*720),
- 목표는 FHD(1920*1080)에서 사용될 사진 자료
폴더명 규칙
- original,1080p,720p,180p
파일명 규칙
- YYYYMMDDHHmmss
- date(“YmdHis”);
- time();
- uploader_test.html
<h1>Image gallery</h1>
<style>
.upload-box{
min-width: 200px;
min-height: 200px;
border: solid 1px black;
}
</style>
<div class="upload-box">
<img id="image_target" src="" />
<form id="form_target" enctype="multipart/form-data" action="http://image.onethelab.com/image_upload.php" method="post">
<label>select file, drag&drop, ctrl+v</label>
<input type="hidden" name="name" value="default" />
<input id="image_file" name="image" class="btn-file d-none" type="file" /> <!--파일 input box 형태-->
<input type="submit" value="submit" />
</form>
</div>
<script>
var uploadBox = document.querySelector('.upload-box');
uploadBox.addEventListener('dragover', function(e) {
e.preventDefault();
});
/* 박스 안에서 Drag를 Drop했을 때 */
uploadBox.addEventListener('drop', function(e) {
e.preventDefault();
const data = e.dataTransfer;
if(!isValid(data)) return; //유효성 Check
document.querySelector('#image_file').files = data.files;
document.querySelector('#image_target').src = URL.createObjectURL(data.files[0]);
});
let image_target = document.querySelector('#image_file');
image_target.addEventListener("change", dataUpdate);
function dataUpdate(e){
const data = e.dataTransfer;
if(!isValid(image_target)) return; //유효성 Check
document.querySelector('#image_target').src = URL.createObjectURL(image_target.files[0]);
}
////
function isValid(data){
//파일인지 유효성 검사
if(!isNull(data.types) && data.types.indexOf('Files') < 0) {alert("exit1"); return false;}
//이미지인지 유효성 검사
if(data.files[0].type.indexOf('image') < 0){
alert('이미지 파일만 업로드 가능합니다.'+data.files[0].type);
return false;
}
//파일의 개수는 1개씩만 가능하도록 유효성 검사
if(data.files.length > 1){
alert('파일은 하나씩 전송이 가능합니다.');
return false;
}
//파일의 사이즈는 50MB 미만
if(data.files[0].size >= 1024 * 1024 * 50){
alert('50MB 이상인 파일은 업로드할 수 없습니다.');
return false;
}
return true;
}
addEventListener("paste", onPaste);
function onPaste(event) {
if (event.clipboardData.files.length) {
let files = event.clipboardData.files;
document.querySelector('#image_file').files = files;
document.querySelector('#image_target').src = URL.createObjectURL(files[0]);
}
}
function isNull(v) {
return (v === undefined || v === null) ? true : false;
}
</script>
- cropper_test.html
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<!-- <script src="https://fengyuanchen.github.io/cropper/js/cropper.js"></script> -->
<link href="node_modules/cropperjs/dist/cropper.css" rel="stylesheet">
<script src="node_modules/cropperjs/dist/cropper.js"></script>
<!-- Wrap the image or canvas element with a block element (container) -->
<style>
/* Make sure the size of the image fits perfectly into the container */
img {
display: block;
/* This rule is very important, please don't ignore this */
max-width: 100%;
}
</style>
<div>
<img id="image" src="picture.jpg" />
</div>
<div id="preview" style="overflow: hidden;">
<img src="picture.jpg" />
</div>
<input type="button" onclick="setImage();" value="set image" />
<img id="test" />
<script>
//import 'cropperjs/dist/cropper.css';
//import Cropper from 'cropperjs';
const image = document.getElementById('image');
const preview = document.getElementById('preview');
const cropper = new Cropper(image, {
aspectRatio: 16 / 9,
preview: preview,
crop(event) {
console.log(event.detail.x);
console.log(event.detail.y);
console.log(event.detail.width);
console.log(event.detail.height);
console.log(event.detail.rotate);
console.log(event.detail.scaleX);
console.log(event.detail.scaleY);
},
});
let img_target = document.querySelector("#image_file");
let form_target = document.querySelector("#form_target");
let test_target = document.querySelector("#test");
let formdata = new FormData();
let canvas_;
function setImage(){
//cropper.getCroppedCanvas({ maxWidth: 4096, maxHeight: 4096 })
//img_target.crossOrigin = "anonymous";
//img_target.file = cropper.getCroppedCanvas({ maxWidth: 4096, maxHeight: 4096 }).toDataURL("image/jpg");
canvas_ = cropper.getCroppedCanvas({ maxWidth: 2048, maxHeight: 2048 })
let blob_ = canvas_.toBlob((blob)=>{
file = new File([blob], 'cropped.jpg', { type: 'image/jpeg' }); //blob을 가지고 파일을 생성
let url = window.URL.createObjectURL(blob);
test_target.src = url;
//여기까진 성공
submit();
}, "image/jpeg", 1.0);
}
async function submit() {
let imageBlob = await new Promise(resolve =>
cropper.getCroppedCanvas({ maxWidth: 2048, maxHeight: 2048 }).toBlob(resolve, 'image/jpeg')
);
console.log("step1");
let formData = new FormData();
formData.append("firstName", "Bora");
formData.append("image", imageBlob, "cropped.jpg");
console.log("step2");
let response = await fetch('http://image.onethelab.com/image_upload.php', {
method: 'POST',
body: formData
});
let result = await response;
console.log(result);
}
</script>
complex.html
- 위 두버전을 합친 형태 cropper를 이미지가 교체될때 마다 destroy 하고 새로 생성해야(new) 했다
<!-- ■ uploader start -->
<h1>Image gallery</h1>
<style>
.upload-box{
min-width: 200px;
min-height: 200px;
border: solid 1px black;
}
</style>
<div class="upload-box">
<img id="image_target" src="" />
<form id="form_target" enctype="multipart/form-data" action="http://image.onethelab.com/image_upload.php" method="post">
<label>select file, drag&drop, ctrl+v</label>
<input type="hidden" name="name" value="default" />
<input id="image_file" name="image" class="btn-file d-none" type="file" /> <!--파일 input box 형태-->
<!-- <input type="submit" value="submit" /> -->
</form>
</div>
<div>
test
<img id="image" src="picture.jpg" />
</div>
<div id="preview" style="overflow: hidden;">
<img id="crop_target" src="picture.jpg" />
</div>
<input type="button" onclick="setImage();" value="set image" />
<script>
let crop_target = document.querySelector("#image");
var uploadBox = document.querySelector('.upload-box');
uploadBox.addEventListener('dragover', function(e) {
e.preventDefault();
});
/* 박스 안에서 Drag를 Drop했을 때 */
uploadBox.addEventListener('drop', function(e) {
e.preventDefault();
const data = e.dataTransfer;
if(!isValid(data)) return; //유효성 Check
document.querySelector('#image_file').files = data.files;
//document.querySelector('#image_target').src = URL.createObjectURL(data.files[0]);
del_cropper();
crop_target.src = "";
crop_target.src = URL.createObjectURL(data.files[0]);
set_cropper();
});
let image_target = document.querySelector('#image_file');
image_target.addEventListener("change", dataUpdate);
function dataUpdate(e){
const data = e.dataTransfer;
if(!isValid(image_target)) return; //유효성 Check
//document.querySelector('#image_target').src = URL.createObjectURL(image_target.files[0]);
del_cropper();
crop_target.src = "";
crop_target.src = URL.createObjectURL(image_target.files[0]);
set_cropper();
}
////
function isValid(data){
//파일인지 유효성 검사
if(!isNull(data.types) && data.types.indexOf('Files') < 0) {alert("exit1"); return false;}
//이미지인지 유효성 검사
if(data.files[0].type.indexOf('image') < 0){
alert('이미지 파일만 업로드 가능합니다.'+data.files[0].type);
return false;
}
//파일의 개수는 1개씩만 가능하도록 유효성 검사
if(data.files.length > 1){
alert('파일은 하나씩 전송이 가능합니다.');
return false;
}
//파일의 사이즈는 50MB 미만
if(data.files[0].size >= 1024 * 1024 * 50){
alert('50MB 이상인 파일은 업로드할 수 없습니다.');
return false;
}
return true;
}
addEventListener("paste", onPaste);
function onPaste(event) {
if (event.clipboardData.files.length) {
let files = event.clipboardData.files;
document.querySelector('#image_file').files = files;
//document.querySelector('#image_target').src = URL.createObjectURL(files[0]);
del_cropper();
crop_target.src = "";
crop_target.src = URL.createObjectURL(files[0]);
set_cropper();
}
}
function isNull(v) {
return (v === undefined || v === null) ? true : false;
}
</script>
<!-- ■ cropper start -->
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<!-- <script src="https://fengyuanchen.github.io/cropper/js/cropper.js"></script> -->
<link href="node_modules/cropperjs/dist/cropper.css" rel="stylesheet">
<script src="node_modules/cropperjs/dist/cropper.js"></script>
<!-- Wrap the image or canvas element with a block element (container) -->
<style>
/* Make sure the size of the image fits perfectly into the container */
img {
display: block;
/* This rule is very important, please don't ignore this */
max-width: 100%;
}
</style>
<script>
//import 'cropperjs/dist/cropper.css';
//import Cropper from 'cropperjs';
const image = document.getElementById('image');
const preview = document.getElementById('preview');
let cropper;
function del_cropper(){
//alert(cropper);
if(cropper != null){
//alert("destroy!");
cropper.destroy();
}
}
function set_cropper(){
cropper = new Cropper(image, {
aspectRatio: 16 / 9,
preview: preview,
crop(event) {
console.log(event.detail.x);
console.log(event.detail.y);
console.log(event.detail.width);
console.log(event.detail.height);
console.log(event.detail.rotate);
console.log(event.detail.scaleX);
console.log(event.detail.scaleY);
},
});
}
let img_target = document.querySelector("#image_file");
let form_target = document.querySelector("#form_target");
let formdata = new FormData();
let canvas_;
function setImage(){
//cropper.getCroppedCanvas({ maxWidth: 4096, maxHeight: 4096 })
//img_target.crossOrigin = "anonymous";
//img_target.file = cropper.getCroppedCanvas({ maxWidth: 4096, maxHeight: 4096 }).toDataURL("image/jpg");
alert(cropper);
canvas_ = cropper.getCroppedCanvas({ maxWidth: 2048, maxHeight: 2048 })
let blob_ = canvas_.toBlob((blob)=>{
file = new File([blob], 'cropped.jpg', { type: 'image/jpeg' }); //blob을 가지고 파일을 생성
let url = window.URL.createObjectURL(blob);
//test_target.src = url;
//여기까진 성공
submit();
}, "image/jpeg", 1.0);
}
async function submit() {
let imageBlob = await new Promise(resolve =>
cropper.getCroppedCanvas({ maxWidth: 2048, maxHeight: 2048 }).toBlob(resolve, 'image/jpeg')
);
console.log("step1");
let formData = new FormData();
formData.append("firstName", "Bora");
formData.append("image", imageBlob, "cropped.jpg");
console.log("step2");
let response = await fetch('http://image.onethelab.com/image_upload.php', {
method: 'POST',
body: formData
});
let result = await response;
console.log(result);
}
</script>