はじめに
実務で、プロフィールで顔写真を登録できるようにしたいと要望がありました。
- プロフィール写真は表示回数が多いため、ファイルサイズを小さくする必要がある
- プロフィール写真を表示する際に、ちょうど良くなるように写真を切り取りできるようにする必要がある
- 写真を切り取り後に、プレビューできるようにする
- ユーザーが使いやすいUIにする
上記の点を意識して、実装を行いました。
今回写真切り取り機能を実装するにあたり、以下のQiitaの記事を大いに参考にさせていただきました。
実装サンプル
See the Pen Untitled by Shogo Watanabe (@Shogo-Watanabe) on CodePen.
実装したコードについて
① 写真を選択後、モーダルに画像を表示
「画像を変更する」ボタンを押して、ファイルを選択後、ファイルのブラウザ上でのURLを取得し、モーダルに表示させる。
② 画像を切り抜き処理
crop_img()の部分で、画像の切り抜きを行い、JPEG形式に変換
③ 切り抜いた画像情報をを渡す。
toDataURL()でJPEG形式に変換したデータURLをに渡し、Controller側で保存することが可能になる。
const body = document.querySelector('html body')
const overlay = document.querySelector('.overlay')
const modal_img = document.querySelector('#modal_img');
const imgPreview = document.getElementById('img-preview');
const imageUrl = document.querySelector('#image_url');
const scal = document.querySelector('#scal')
const cancel = document.querySelector('#cancel');
const crop = document.querySelector('#crop');
const cvs = document.querySelector('#cvs');
const cw = cvs.width
const ch = cvs.height
const out = document.getElementById( 'out' )
const oh = out.height
const ow = out.width
let ix = 0 // 中心X座標
let iy = 0 // 中心Y座標
let v = 1.0 // 拡大率
/* 同じ画像を再度選択した際にモーダル表示されるようにする */
$('#form_image').on('click',function(e) {
e.target.value = '';
})
/* 画像選択時にモーダル表示 */
$('#form_image').on('change', function () {
const file = $(this).prop('files')[0];
// ファイルのブラウザ上でのURLを取得する
const blobUrl = window.URL.createObjectURL(file);
// img要素に表示
modal_img.src = blobUrl;
const img = new Image();
img.onload = function( _ev ){
ix = img.width / 2
iy = img.height / 2
let scl = parseInt( cw / img.width * 100 )
document.getElementById( 'scal' ).value = scl
scaling( scl )
}
load_img(modal_img.src)
function load_img( _url ){
img.src = ( _url ? _url : 'https://1.bp.blogspot.com/-AoQB8vIvlcw/W8BOcXcEQ6I/AAAAAAABPZM/rXNbol90tXcxBZBlXsg__xix03b_F4nqwCLcBGAs/s800/pet_cat_omoi_sleep_man.png' )
}
scal.oninput = function(){
scaling(scal.value);
}
function scaling( _v ) {
v = parseInt( _v ) * 0.008
draw_canvas( ix, iy )
}
/* 画像を更新する */
function draw_canvas( _x, _y ){
const ctx = cvs.getContext( '2d' )
ctx.fillStyle = 'rgb(200, 200, 200)'
ctx.fillRect( 0, 0, cw, ch ) // 背景を塗る
ctx.drawImage( img,
0, 0, img.width, img.height,
(cw/2)-_x*v, (ch/2)-_y*v, img.width*v, img.height*v,
)
ctx.beginPath();
ctx.strokeStyle = 'rgba(0, 0, 0, 0.8)';
ctx.arc( cw/2, ch/2, 75, 0, Math.PI * 2, false);
ctx.stroke();
// ctx.strokeRect( (cw-ow)/2, (ch-oh)/2, ow, oh ) // 赤い枠
}
/* 「決定」ボタンで画像を切り抜き */
crop.addEventListener('click', () => {
crop_img();
})
/* 「キャンセル」ボタンでモーダルを閉じる */
cancel.addEventListener('click', () => {
overlay.classList.add('none');
// body.style.overflow = 'auto';
})
function crop_img(){
const ctx = out.getContext( '2d' )
ctx.fillStyle = 'rgb(200, 200, 200)'
ctx.fillRect( 0, 0, ow, oh ) // 背景を塗る
ctx.drawImage( img,
0, 0, img.width, img.height,
(ow/2)-ix*v*2, (oh/2)-iy*v*2, img.width*v*2, img.height*v*2,
)
const img_url = out.toDataURL('image/jpeg' , 0.8);
imgPreview.src = img_url;
imageUrl.value = img_url;
overlay.classList.add('none');
// body.style.overflow = 'auto';
}
overlay.classList.remove('none');
// body.style.overflow = 'hidden';
let mouse_down = false // canvas ドラッグ中フラグ
let sx = 0 // canvas ドラッグ開始位置
let sy = 0
cvs.ontouchstart =
cvs.onmousedown = function ( _ev ){ // canvas ドラッグ開始位置
mouse_down = true
if(_ev.pageX == undefined){
sx = _ev.touches[0].pageX
}else{
sx = _ev.pageX
}
if(_ev.pageY == undefined){
sy = _ev.touches[0].pageY
}else{
sy = _ev.pageY
}
return false // イベントを伝搬しない
}
cvs.ontouchend =
cvs.onmouseout =
cvs.onmouseup = function ( _ev ){ // canvas ドラッグ終了位置
if ( mouse_down == false ) return
mouse_down = false
if(_ev.pageX == undefined){
draw_canvas( ix += (sx - _ev.changedTouches[0].pageX)/v, iy += (sy - _ev.changedTouches[0].pageY)/v )
}else{
draw_canvas( ix += (sx-_ev.pageX)/v, iy += (sy-_ev.pageY)/v )
}
return false // イベントを伝搬しない
}
cvs.ontouchmove =
cvs.onmousemove = function ( _ev ){ // canvas ドラッグ中
if ( mouse_down == false ) return
if(_ev.pageX == undefined){
draw_canvas( ix + (sx - _ev.touches[0].pageX)/v, iy + (sy - _ev.touches[0].pageY)/v)
}else{
draw_canvas( ix + (sx-_ev.pageX)/v, iy + (sy-_ev.pageY)/v )
}
return false // イベントを伝搬しない
}
cvs.onmousewheel = function ( _ev ){ // canvas ホイールで拡大縮小
let scl = parseInt( parseInt( document.getElementById( 'scal' ).value ) + _ev.wheelDelta * 0.05 )
if ( scl < 10 ) scl = 10
if ( scl > 400 ) scl = 400
document.getElementById( 'scal' ).value = scl
scaling( scl )
return false // イベントを伝搬しない
}
});
さいごに
まだ改善する部分があると思いますので、ご指摘いただけますと幸いです。