Effect
cropperjs crops the picture
Implementation ideas
Get the array to be cropped passed by the parent component
The mounted stage judges whether the current image type is a gif image, and performs different operations to instantiate the components and save them in the data
croppering ( ) {
if ( this . imageUrl[ this . index] . isGif) {
this . gifInit ( ) ;
} else {
this . init ( ) ;
}
} ,
Normal image manipulation
init ( ) {
this . myCropper = new Cropper ( this . $refs. image, {
viewMode : 2 ,
dragMode : 'crop' ,
initialAspectRatio : 1 ,
checkOrientation : false ,
checkCrossOrigin : false ,
guides : false ,
center : false ,
background : false ,
autoCropArea : 0.8 ,
zoomOnWheel : false ,
movable : false ,
rotatable : false ,
scalable : false ,
zoomOnTouch : false ,
} ) ;
} ,
When clicking next, the current image is cropped and output, and passed to the parent component, and judges whether there is still a need to crop the image
this . afterImg = this . myCropper. getCroppedCanvas ( {
imageSmoothingQuality : 'high' ,
} ) . toDataURL ( 'image/jpeg' ) ;
this . index++ ;
if ( this . imageUrl. length > this . index) {
this . $emit ( 'imgCropped' , {
src : this . afterImg, id : this . imageUrl[ this . index - 1 ] . id } ) ;
this . nextImg ( ) ;
} else {
this . $emit ( 'imgCropped' , {
src : this . afterImg, id : this . imageUrl[ this . index - 1 ] . id } ) ;
this . $emit ( 'closeCropper' ) ;
}
Since cropperjs does not support gif cropping, it is necessary to introduce libgif.js (split gif images) and gif.js (merge gif images), and then perform cropping operations like ordinary images
gif image cropping initialization
gifInit ( ) {
this . pre_load_gif ( this . imageUrl[ this . index] . url) ;
} ,
libgif.js splits gif and saves the picture in img_list
dataURLtoFile ( dataurl, filename ) {
const arr = dataurl. split ( ',' ) ;
const mime = arr[ 0 ] . match ( / :(.*?); / ) [ 1 ] ;
const bstr = atob ( arr[ 1 ] ) ;
let n = bstr. length;
const u8arr = new Uint8Array ( n) ;
while ( n-- ) {
u8arr[ n] = bstr. charCodeAt ( n) ;
}
return new File ( [ u8arr] , filename, {
type : mime } ) ;
} ,
convertCanvasToImage ( canvas, filename ) {
return this . dataURLtoFile ( canvas. toDataURL ( 'image/png' ) , filename) ;
} ,
base64ToBlob ( base64 ) {
const parts = base64. split ( ';base64,' ) ;
const contentType = parts[ 0 ] . split ( ':' ) [ 1 ] ;
const raw = window. atob ( parts[ 1 ] ) ;
const rawLength = raw. length;
const uInt8Array = new Uint8Array ( rawLength) ;
for ( let i = 0 ; i < rawLength; i += 1 ) {
uInt8Array[ i] = raw. charCodeAt ( i) ;
}
return new Blob ( [ uInt8Array] , {
type : contentType } ) ;
} ,
pre_load_gif ( gif_source ) {
const gifImg = document. createElement ( 'img' ) ;
const gif = this . base64ToBlob ( gif_source) ;
gifImg. setAttribute ( 'rel:animated_src' , URL . createObjectURL ( gif) ) ;
gifImg. setAttribute ( 'rel:auto_play' , '1' ) ;
document. body. appendChild ( gifImg) ;
const rub = new SuperGif ( {
gif : gifImg } ) ;
rub. load ( ( ) => {
const img_list = [ ] ;
for ( let i = 1 ; i <= rub. get_length ( ) ; i++ ) {
rub. move_to ( i) ;
const cur_file = this . convertCanvasToImage ( rub. get_canvas ( ) , ` 'test'- ${
i} ` ) ;
img_list. push ( {
file_name : cur_file. name,
url : URL . createObjectURL ( cur_file) ,
file : cur_file,
} ) ;
}
this . img_list = img_list;
this . gifCropper ( ) ;
} ) ;
gifImg. remove ( ) ;
} ,
Crop each frame of picture according to the previous idea, if there are too many pictures, it will be automatically cropped
gifCropper ( ) {
this . showItemImg = this . img_list[ this . img_list_index] . url;
this . $nextTick ( ( ) => {
this . myCropper = new Cropper ( this . $refs. image, {
viewMode : 1 ,
dragMode : 'none' ,
checkOrientation : false ,
checkCrossOrigin : false ,
guides : false ,
center : false ,
background : false ,
autoCropArea : 1 ,
zoomOnWheel : false ,
movable : false ,
rotatable : false ,
scalable : false ,
zoomOnTouch : false ,
ready : ( e ) => {
this . sureSavaGif ( e. srcElement. width) ;
} ,
} ) ;
} ) ;
} ,
sureSavaGif ( imgWidth ) {
if ( imgWidth > 800 ) {
imgWidth = 800 ;
}
const afterImg = this . myCropper. getCroppedCanvas ( {
imageSmoothingQuality : 'height' ,
width : imgWidth,
} ) . toDataURL ( 'image/jpeg' ) ;
this . eachGif. push ( afterImg) ;
this . myCropper. destroy ( ) ;
this . img_list_index++ ;
if ( this . img_list. length > this . img_list_index) {
this . $nextTick ( ( ) => {
this . gifCropper ( ) ;
} ) ;
} else {
this . mergeGif ( ) ;
}
} ,
async mergeGif ( ) {
const gif = new GIF ( {
workers : 2 ,
quality : 10 ,
workerScript : getGifWorker ( ) ,
} ) ;
let j = 0 ;
const canvas = document. createElement ( 'canvas' ) ;
const ctx = canvas. getContext ( '2d' ) ;
for ( let i = 1 ; i <= this . eachGif. length; i++ ) {
const imgImage = await this . loading ( i) ;
canvas. width = imgImage. width;
canvas. height = imgImage. height;
ctx. fillStyle = '#fff' ;
ctx. fillRect ( 0 , 0 , canvas. width, canvas. height) ;
ctx. drawImage ( imgImage, 0 , 0 , canvas. width, canvas. height) ;
gif. addFrame ( canvas, {
copy : true , delay : 50 } ) ;
j++ ;
if ( j >= this . eachGif. length) {
gif. render ( ) ;
}
}
gif. on ( 'finished' , async ( blob ) => {
const result = await this . blobToBase64 ( blob) ;
console. log ( result) ;
console. log ( this . index) ;
this . $emit ( 'imgCropped' , {
src : result, id : this . imageUrl[ this . index] . id } ) ;
gif. abort ( ) ;
const gifsNode = document. getElementsByClassName ( 'jsgif' ) ;
console. log ( gifsNode[ 0 ] ) ;
gifsNode[ 0 ] . remove ( ) ;
this . index++ ;
console. log ( this . imageUrl) ;
if ( this . imageUrl. length > this . index) {
this . eachGif = [ ] ;
this . img_list_index = 0 ;
this . croppering ( ) ;
} else {
this . index = 0 ;
this . img_list_index = 0 ;
this . $emit ( 'closeCropper' ) ;
}
} ) ;
} ,
loading ( i ) {
return new Promise ( ( resolve ) => {
const imgImage = new Image ( ) ;
imgImage. src = this . eachGif[ i - 1 ] ;
document. body. appendChild ( imgImage) ;
imgImage. onload = ( ) => {
resolve ( imgImage) ;
imgImage. parentNode. removeChild ( imgImage) ;
} ;
} ) ;
} ,
blobToBase64 ( blob ) {
return new Promise ( ( resolve, reject ) => {
const fileReader = new FileReader ( ) ;
fileReader. onload = ( e ) => {
resolve ( e. target. result) ;
} ;
fileReader. readAsDataURL ( blob) ;
fileReader. onerror = ( ) => {
reject ( new Error ( 'blobToBase64 error' ) ) ;
} ;
} ) ;
} ,
Click Close to close the clipper
goBack ( ) {
this . $emit ( 'closeCropper' ) ;
} ,
Photoswipe realizes the preview operation of pictures
addressphotoswipe _
Implementation ideas
The parent component needs to pass three parameters
Whether to preview, is to perform preview operation switch
Preview index, initially, that is, the picture that is seen after the preview component is instantiated
preview array, including all img
props : {
isPrview : {
type : Boolean,
} ,
previewIndex : {
type : Number,
} ,
previewImg : {
type : Array,
} ,
} ,
Monitor isPrview, when it is true, perform preview initialization
watch : {
isPrview ( val ) {
if ( val === true ) this . initPhotoSwiper ( ) ;
} ,
} ,
initPhotoSwiper ( ) {
const {
pswp } = this . $refs;
const options = {
index : this . previewIndex,
} ;
this . gallery = new PhotoSwiper ( pswp, UI , this . previewImg, options) ;
this . gallery. init ( ) ;
this . gallery. listen ( 'close' , ( ) => {
const info = this . previewImg. filter ( ( item ) => item. completed) . map ( ( item ) => ( {
src : item. src,
id : item. id,
} ) ) ;
console. log ( info) ;
this . $emit ( 'changImageUrl' , info) ;
} ) ;
this . gallery. listen ( 'beforeChange' , ( ) => {
this . isactive = this . previewImg[ this . gallery. getCurrentIndex ( ) ] . completed;
} ) ;
} ,
When the custom button is clicked, toggle the selected state
checkboxClick ( ) {
this . previewImg[ this . gallery. getCurrentIndex ( ) ] . completed = ! this . previewImg[ this . gallery. getCurrentIndex ( ) ] . completed;
this . isactive = this . previewImg[ this . gallery. getCurrentIndex ( ) ] . completed;
} ,
When the preview is closed, the array is filtered according to the completed (true is selected) of each picture in the array
Parent component HomeView.vue
After uploading the image, reading the file is an asynchronous operation, which needs to be processed synchronously through Promise
syncFile ( file ) {
return new Promise ( ( resolve, reject ) => {
const reader = new FileReader ( ) ;
reader. readAsDataURL ( file) ;
reader. onload = function ( e ) {
resolve ( e) ;
} ;
reader. onerror = ( ) => {
reject ( ) ;
} ;
} ) ;
} ,
After reading the file, temporarily save it in the imageUrl array, and enable the cropper to crop the image
async imgChange ( e ) {
const {
files } = e. target;
for ( let i = 0 ; i < files. length; i++ ) {
const rederUrl = await this . syncFile ( files[ i] ) ;
console. log ( rederUrl) ;
if ( this . imageUrl. length === 0 ) {
this . imageUrl. push ( {
id : rederUrl. loaded,
url : rederUrl. target. result,
} ) ;
} else {
const status = this . imageUrl. some ( ( item ) => item. id === rederUrl. loaded) ;
if ( ! status) {
this . imageUrl. push ( {
url : rederUrl. target. result,
id : rederUrl. loaded,
} ) ;
}
}
}
this . showCropper = true ;
} ,
Because cropperjs does not support gif cropping, it is necessary to add whether it is a gif image in the information, and perform different cropping operations
const result = files[ i] . name. split ( '.' ) [ 1 ] ;
if ( result === 'gif' ) {
this . isGif = true ;
} else {
this . isGif = false ;
}
The cropped image is saved in perImg, and if there is a file in the perImg array, it will be rendered to the page to provide a thumbnail preview
imgCropped ( data ) {
this . perImg. push ( {
src : data. src,
id : data. id,
} ) ;
} ,
When the picture is clicked, the index of the clicked picture is passed in, and the data structure of the picture is processed, and then the preview component is invoked
this . $refs. img. forEach ( ( item, index ) => {
this . perImg[ index] . w = item. offsetWidth;
this . perImg[ index] . h = item. offsetHeight;
this . perImg[ index] . completed = true ;
} ) ;
this . previewIndex = index;
this . isPrview = true ;
} )
It is possible to cancel the image operation during preview, so after closing the preview, reassign perImg
changImageUrl ( data ) {
this . perImg = data;
this . isPrview = false ;
}