Inside the Croppr frontend
I learned allot creating croppr. It’s pretty intersting what you can do with the DOM and some CSS. Let’s take a peek under the hood.
The first thing croppr does upon instantiation is create a bunch of DOM elements. Here’s what that looks like.
<div style="background: white;
position: absolute;
top: 0px; left: 0px;
width: 100%; height: 545px;
opacity: 0.8;"/>
<div id="croppr" style="overflow: hidden;
position: absolute;
top: 0px; left: 0px;
width: 100%; height: 545px;">
<div style="border: 1px solid #000000;
overflow: hidden;
position: absolute;
top: 50%; left: 50%;
width: 118px; height: 118px;
margin-top: -60px; margin-left: -60px;">
<img src="/croppr/images/1" style="cursor: move;
position: absolute;
top: 50%; left: 50%;
margin-top: -272px; margin-left: -357px;
width: 714px; height: 544px;
opacity: 1;"/>
</div>
<img src="/croppr/images/1" style="cursor: move;
position: absolute;
top: 50%; left: 50%;
margin-top: -272px; margin-left: -357px;
width: 714px; height: 544px;
opacity: 0.3;"/>
<a class="exit">
<span>EXIT</span>
</a>
<a class="crop">
<span>CROP</span>
</a>
<div class="track">
<div class="handle" style="cursor: move;
position: relative;
left: 154.312px;"/>
</div>
</div>
Toggle the styles to understand what’s going on here. The first element is the transparency or “light box” that dims whatever else is on the screen (trans). The order of the elements is important. We want the transparency to be all the way in the back. Elements later in the DOM that are absolutely positioned will be infront of previous absolutely positioned elements (unless we set the z-index property).
The next element is our container for all the fun goodies we’re about to add (container). It serves three purposes.
- Makes cleanup easy. By deleting this element, all the others will disappear.
- Namespaces the rest of our elements. You can target
#croppr .exitin your CSS without effecting other elements with the class ofexit. - Get’s rid of vertical and horizontal scroll bars. By setting
overflow:hiddenwhen the image we are cropping goes outside of the window, the browser doesn’t add scrollbars and screw up our math.
The transparent box is not placed in this div because we want a nice fade back into the original environment after we have deleted all of the cropping tools. More on this in a little bit.
Next we have the mask. The width and height of the mask is set to the width and height of the desired crop - 2. We subtract two because it has a 1 pixel border and the box model includes the border in the width and height. A copy of the main image (ghost) is put inside of the mask. overflow:hidden is set on the mask as well, making the parts outside of it invisible. Both the mask and the ghost are positioned absolutely and have top and left attributes set to 50%. We then give them negative top and left margins. The mask’s margin’s will always be half of its width and height, while the ghost’s margins are based on it’s current position. This puts the work of keeping the items centered if the window resizes in the hands of the browser. All of this is done rather elegantly with mootools:
this.mask = new Element("div").setStyles({
border: "1px solid #000",
overflow: "hidden",
position: "absolute",
width: this.options.crop[0]-2+"px",
height: this.options.crop[1]-2+"px",
top: "50%",
left: "50%",
marginTop: -this.options.crop[1]/2+"px",
marginLeft: -this.options.crop[0]/2+"px"
}).injectInside(this.container);
this.ghost = this.image.clone(false).injectInside(this.mask);
The image itself is next up. She is infront of the mask so we can drag her around without having the mask conflict. We set the opacity to .3 making the ghosting effect. The positioning of this image is identical to the one inside the mask. Infact, as you drag the image, it’s css is copied straight into the css of the image inside of the mask. The only difference is the opacity.
draw: function() {
this.ghost.style.cssText = this.image.style.cssText;
this.ghost.setOpacity(1);
}
this.drag = new Drag.Base(this.image, {
limit: {
x: [-this.size[0] + this.options.crop[0] / 2, -this.options.crop[0] / 2],
y: [-this.size[1] + this.options.crop[1] / 2, -this.options.crop[1] / 2]
},
modifiers: {
x: "margin-left",
y: "margin-top"
},
onDrag: this.draw.bind(this)
});
Mootool’s awesome Drag class provides a way to constrain the object being drug via the limit parameter in the constructor. This logic is repeated in the scaling function (updateSlide) to insure the image is not scaled outside of the mask. This function is the hairiest of them all… maybe we’ll dig into her another time.
Last but not least we have the CROP and EXIT buttons along with the slider itself. These need to be infront of everything else, which is why they are last. There is no styling done explicitly by the croppr javascript library. Instead, it gives you the freedom to style them however you would like.
When we’re done using croppr, cleanup is as easy as:
this.container.remove();
new Fx.Style(this.trans, 'opacity', {duration: 1000, onComplete: this.trans.remove}).start(0.8, 0);
And that’s that, bending the DOM to do your dirty work turns out not to be so bad…
Croppr, ready for primetime
It’s been a long time coming… Croppr has grown out of it’s infancy. Sparked recently by the release of Gravatar 2.0 and the Ruby on Rails podcast Camping II, I dove back into the code and cleaned it up. Croppr now adheres to the minimalistic principles of Camping. RMagick was thrown out in favor of ImageScience. The javascript was tweaked for performance and readability using the ever so light and powerfull MooTools. The magic is really in the javascript, I encourage you to check it out. All the pieces come together as a living tutorial of how to implement Croppr on your own. Go play with my vain demo! If you want to get sneaky and upload your own image tag /new to the end of the URL, but keep it clean… Here’s an overview on how to get her up and running on your own machine:
sudo gem install mongrel
sudo gem install camping
sudo gem install json
sudo gem install sqlite3-ruby (you need to have sqlite3 installed)
sudo gem install image_science (you need to have freeimage installed)
svn co http://svn.vandev.com/croppr/trunk croppr
cd croppr
ruby croppr.rb
It’s that easy. You should be able to follow the documentation in the javascript and the implementation example in the camping app to get her working yourself. Goodluck, and let me know of any crazy browser issues. I half-assed tested it in IE 6 and 7… Safari and Firefox are golden.
Croppr podcast... a little late
The SDRuby podcast site is filling up with great episodes. One of which being my little talk on camping and Croppr (Episode 002 on the bottom). The thing has been up for a couple of monthes now, but my posting consistency has been miserable lately. I’ve had more pressing issues, but today is a new day. My next project will be pushing this thing over to Mephisto. I should also spend some time fixing the Rails date_select helper, but that’s another post all together. The next time I speak, the entire back end of this puppy will be different… totally awesome.
Croppr is born... perhaps pre-mature
So, I finally got around to testing TextMate’s new blogging bundle. It’s dead simple… Anywho, I figured I would use it to give a sneak peek of my newest invention Croppr, which I will be revealing in it’s full glory tomorrow night at SDruby.
All right, so Croppr was born out of a desire to make a better way to crop images online. Specifically the need came from my buddy Tom Werner’s pet project, Gravatar. The inspiration came from iChat’s avatar cropper:

After days of arduous JavaScript hacking with the help of Prototype and eventually Prototype Lite with moo.fx I had a lightweight browser equivalent. Have a peek and play around.
What good is a lightweight JavaScript cropper without a lightweight back end to actually do the cropping? Enter camping. In just 225 lines of sexy Ruby code I have a fully functional image cropping machine. I hope to have a running demo available in the near future. As far as licensing goes, I’m still working on it so hold your shorts. Until than, come to the SDruby meetup (start driving now if you have to) and see all the awesomeness that is Croppr!







