Computer Forum - by Slash Forum Index -> Web Design -> image zone selection crop thing

 
Post new topic   Reply to topic View previous topic :: View next topic  


 
Author Message
Slash
Site Admin


Joined: 29 Mar 2007
Posts: 9
Location: Bucharest


Fri Jun 22, 2007 1:59 pm
PostPost subject: image zone selection crop thing Reply with quote

Yeah sorry for the stupid title, I just don't know how to call it.

I'm redoing my website, a 3d portfolio, and I'm using mootool for that. Eventhough it's a personnal project, I thought it would be a greate way to learn, and develop tools that I could re-use in the future, while I don't really need them right now. I mean, I don't really need an image uploader when I can just open an ftp session and put it here. Anyway, I thought it would be cool to be able to upload an image, and then being able to generate a thumbnail for the gallery, by just selecting an area on the picture, instaned of just resizing the original picture. So I wrote this :

DEMO: Ulei de rodie

it uses Drag.Move, and makeResizable(). It displays a selection box over an image, greying out the outside, and let you drag resize it. All you have to do is providing the image element/id when you create the object. The default options are the one that fit my needs but they can be changed.

It's functionnal, but I'm still learning to use Mootools, so I'm sure it's not perfect and the code can be improved. So you guys feel free to take a look at it and any critisze/suggestion/correction is welcome. I've only tested it one my computer which is very fast, so I'm affraid of how it would behave on slower computer, since it does a lot of stuff onDrag event. Tested in firefox, opera and IE7 (used a hack for that one, please check the code, there might be a better way).
Quote:
/*
Class: Cropper

Note:
Originally written to allow user to generate custom thumbnails after uploading an image to the server.
User selects a zone on the picture, and then the coordinates can be sent to the server where the actual image file will be processed.
I thought it would also be usefull to create highlight zones, like on flickr. It was written for images, but it can be used with div too.
Still work in progress. I don't think it works if image is inside an overflown element, but it should be easy to fix.
Tested in Firefox2, Opera9 and IE7. Couldn't test it in Safari yet, don't have a mac. IE uses a hack for now, described in the code.

Arguments:
target - the new object receives the picture element/id.
options - see Options bellow

Options:
mask - if a mask should be overlayed on the picture, representing the non-selected area. Default is true
maskColor - string representing the mask's color. Default is #000000
maskOpacity - the opacity of the mask. Default is 0.3
borderWidth - Width of the selection border. Default is 2px (you can provide an int or a string 'Xpx', it is convert to int anyway)
borderStyle - Style of the selection border. Default is dashed
borderColor - Color of the selection Border. Defautl is #ff0000
mini - {x,y} minimum size of the selection. Selection will be initialised with this values. Default are 80 pixels wide and high. (you can provide an int or a string 'Xpx', it is convert to int anyway)
onComplete - function fired when when stop dragging/resizing. receives four parameters: top,left,width,height.
resizerWidth - Size of the resizing zone, on left and bottom of selection. Cursor changes when switching from dragging to resizing mode. default is 8px (you can provide an int or a string 'Xpx', it is convert to int anyway)
resizable - if the selection can be resized. Default is true
keepRatio - if aspect ratio must be kept when resizing, depending on the minimum size of the selection. Default is true

Exemple:
var myCropper = new Cropper('myImage', {
borderWidth: '1px',
BorderStyle: 'dotted',
mini: {x:160, y:120},
onComplete:function(top,left,width,height){
$('resize_coords').value="top:"+ top +"px, left:"+ left +"px, width:"+ width +"px, height:"+ height +"px";
}
});
*/

var Cropper = new Class({

options : {
maskColor:'#000000',
maskOpacity:0.3,
mask:true,
borderWidth:2,
borderStyle:'dashed',
borderColor:'#ff0000',
mini:{x:80,y:80},
onComplete:Class.empty,
resizerWidth:8,
resizable:true,
keepRatio:true
},

initialize : function(target,options){
this.setOptions(options);
this.target=$(target);

//Generating the new elements. see the functions for more details
this.buildCropper();
if(this.options.mask)
{
this.buildMask();
}

//initialize the dragging, using the target element as container, and the selection's dragger as handle
//On complete we fire the optional function, providing the top, left, width and height of the selection a parameters.
//Those coordinates are also stored in the object, so you can access them from an external function, like when presing a button to submit a request to the server.
//On drag we update the mask
this.drag = new Drag.Move(this.resizer,{
container:this.target,
handle:this.dragger,
onComplete:function(){
var coord1 = this.resizer.getCoordinates();
this.top = coord1.top-this.target_coord.top;
this.left = coord1.left-this.target_coord.left;
this.width = coord1.width;
this.height = coord1.height;
this.fireEvent('onComplete',[this.top,this.left,this.width,this.height]);
}.bind(this),
onDrag:function(){
if(this.options.mask)
{
this.updateMask();
}
}.bind(this)
});

//We initializse the resizing object if option resizable is set to true
if(this.options.resizable)
{
//if keepRation is set to true, we initialize a ratio object variable, so we don't have to calculate on every drag event
if(this.options.keepRatio)
{
this.ratio=this.options.mini.x/this.options.mini.y;
}
this.resize = this.resizer.makeResizable({
limit:{
x:[this.options.mini.x.toInt()-this.margin],
y:[this.options.mini.y.toInt()-this.margin]
},
onComplete:function(){
//this is just to fix eventual bug, where the selection extends ouside of the element
this.resize.fireEvent('onDrag');

//Does the same thing as drag onComplete
this.drag.fireEvent('onComplete');
}.bind(this),
onDrag:function(){
//This is the tricky part. It works, but I got some bugs when stress testing it on the bottom and right borders of the element.
//I'm sure there is a better way to do that, so feel free to adjust it.
var coord1=this.resizer.getCoordinates();
if(this.options.keepRatio)
{
this.resizer.setStyle('width',(coord1.height*this.ratio-this.margin).toInt()+'px');
if(coord1.bottom>this.target_coord.bottom)
{
var bound = this.target_coord.bottom-coord1.top;
this.resizer.setStyles({'width':(bound*this.ratio).toInt()-this.margin+'px','height':bound-this.margin+'px'});
}
if(coord1.right>this.target_coord.right)
{
var bound = this.target_coord.right-coord1.left;
this.resizer.setStyles({'width':bound-this.margin+'px','height':(bound/this.ratio).toInt()-this.margin+'px'});
}
}
else
{
if(coord1.right>this.target_coord.right)
{
var bound = this.target_coord.right-coord1.left-this.margin+'px';
this.resizer.setStyles({'width':bound,'height':bound});
}
if(coord1.bottom>this.target_coord.bottom)
{
var bound = this.target_coord.bottom-coord1.top-this.margin+'px';
this.resizer.setStyles({'width':bound,'height':bound});
}
}
//to update the mask
this.drag.fireEvent('onDrag');
}.bind(this)
});
}
if (this.options.initialize) this.options.initialize.call(this);
return this;
},

buildCropper : function(){

//a wrapper element is created. it adopts the target element and inherits its margin, padding and boder
//you may have to edit the wrapper's properties to keep the original look of your page.
//Just use myCropper.wrapper
this.wrapper = new Element('div');
this.wrapper.setStyles({
margin : this.target.getStyle('margin'),
padding : this.target.getStyle('padding'),
border : this.target.getStyle('border')
});
this.wrapper.injectAfter(this.target);
this.wrapper.adopt(this.target);
this.target.setStyles({
margin : 0,
padding : 0,
border : 0
});

//get the target element coordinates, will be used a lot. We suppose the element position doesn't change
this.target_coord=this.target.getCoordinates();

//In our case, it is important that the selection has exactly the dimension we want it to have, because we want to use its coordinates
//So, because the selection element has a margin and a padding(requiered), we must substract thos values everytime we set the selection width or height
//we set it once in the object, so we doesn't have to calculate it everytime
this.margin=2*this.options.borderWidth.toInt() + this.options.resizerWidth.toInt();

//the main selection element, which will be draggable and resizable, generated from the options. It is centered on the target element
this.resizer = new Element('div');
this.resizer.setStyles({
position:'absolute',
display:'block',
border:this.options.borderWidth.toInt() + "px " + this.options.borderStyle + " " + this.options.borderColor,
width:this.options.mini.x.toInt() - this.margin + "px",
height:this.options.mini.y.toInt() - this.margin + "px",
left:(this.target_coord.left+(this.target_coord.width/2)-(this.options.mini.x.toInt()/2)).toInt()+'px',
top:(this.target_coord.top+(this.target_coord.height/2)-(this.options.mini.y.toInt()/2)).toInt()+'px',
padding:'0 ' + this.options.resizerWidth.toInt() + 'px ' + this.options.resizerWidth.toInt()+ 'px 0',
cursor: 'nw-resize'
});
this.resizer.injectAfter(this.target);

//The dragger is an element injected inside the selection element. it will be used as a handle for the Drag.Move object
this.dragger = new Element('div');
this.dragger.setStyles({
display:'block',
width:'100%',
height:'100%',
cursor:'move'
});
this.dragger.injectInside(this.resizer);

//IE hack: in IE, an element doesn'tseem to catch mouse events if it has not content and no 'solid' background.
//So I set a white background with a very low opacity. but problem is the selection boder beocmes transparent too.
//So if you disable the mask effect, you can't even see it at all.
//if a css guru knows of another trick, he his welcome.
if(window.ie)
{
this.resizer.setStyle('backgroundColor','#ffffff');
this.resizer.setOpacity(0.01);
}
},

buildMask : function(){
//to generate the mask, we creat four div, on top, left, right and bottom of the selection
//The padding and margin are set to 0 just for safety, in case you applied global css rules to all your div elements.
//I know it's pretty ugly for now and can be optimized.
this.rezr_coord = this.resizer.getCoordinates();
this.mask_top = new Element('div');
this.mask_top.setStyles({
position:'absolute',
top:this.target_coord.top+'px',
left:this.target_coord.left+'px',
width:this.target_coord.width+'px',
height:this.rezr_coord.top-this.target_coord.top+'px',
backgroundColor:this.options.maskColor,
padding:0,
margin:0
});
this.mask_top.setOpacity(this.options.maskOpacity);
this.mask_left = new Element('div');
this.mask_left.setStyles({
position:'absolute',
top:this.rezr_coord.top+'px',
left:this.target_coord.left+'px',
width:this.rezr_coord.left-this.target_coord.left+'px',
height:this.rezr_coord.height+'px',
backgroundColor:this.options.maskColor,
padding:0,
margin:0
});
this.mask_left.setOpacity(this.options.maskOpacity);
this.mask_right = new Element('div');
this.mask_right.setStyles({
position:'absolute',
top:this.rezr_coord.top+'px',
left:this.rezr_coord.right+'px',
width:this.target_coord.right-this.rezr_coord.right+'px',
height:this.rezr_coord.height+'px',
backgroundColor:this.options.maskColor,
padding:0,
margin:0
});
this.mask_right.setOpacity(this.options.maskOpacity);
this.mask_bottom = new Element('div');
this.mask_bottom.setStyles({
position:'absolute',
top:this.rezr_coord.bottom+'px',
left:this.target_coord.left+'px',
width:this.target_coord.width+'px',
height:this.target_coord.bottom-this.rezr_coord.bottom+'px',
backgroundColor:this.options.maskColor,
padding:0,
margin:0
});
this.mask_bottom.setOpacity(this.options.maskOpacity);
this.mask_top.injectAfter(this.resizer);
this.mask_left.injectAfter(this.resizer);
this.mask_right.injectAfter(this.resizer);
this.mask_bottom.injectAfter(this.resizer);
},

updateMask : function(){
//Made this a function because it's being called when both dragging and resizing
//it's pretty much doing the same thing as the builder, except it uses refreshed coordinates from the selection
var coord1=this.resizer.getCoordinates();
this.mask_top.setStyles({
top:this.target_coord.top+'px',
left:this.target_coord.left+'px',
width:this.target_coord.width+'px',
height:coord1.top-this.target_coord.top+'px'
});
this.mask_left.setStyles({
top:coord1.top+'px',
left:this.target_coord.left+'px',
width:coord1.left-this.target_coord.left+'px',
height:coord1.height+'px'
});
this.mask_right.setStyles({
top:coord1.top+'px',
left:coord1.right+'px',
width:this.target_coord.right-coord1.right+'px',
height:coord1.height+'px'
});
this.mask_bottom.setStyles({
top:coord1.bottom+'px',
left:this.target_coord.left+'px',
width:this.target_coord.width+'px',
height:this.target_coord.bottom-coord1.bottom+'px'
});
}
});
Cropper.implement(new Events, new Options);

Have Fun!!!
_________________
Everything is happening for a reason!
Best Regards,
Slash
Back to top
View user's profile Send private message Visit poster's website

Display posts from previous:   
Post new topic   Reply to topic    Page 1 of 1 All times are GMT

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Forum skin developed by: Slash