That's the sort of experience I'd like to bring to the web - a simple gallery that can be enjoyed by as many people as possible - so after looking at several that I thought were generally lacking, and building a few almost decent galleries for businesses, I put my mind to the task and built what could easily be the lightest semantic gallery - which you can see in the little image to the right - that I've seen. If you want to play around with the code, you can copy the source at http://products.cathmhaol.com/prototypes/scrollablegallery/ - or you can follow along here to get the gist of it.
Markup
As always, I'm going to start off with the markup. The block is wrapped in a DIV tag with the class "gallery". The gallery is composed of three blocks - two buttons separated by a viewport, and within the viewport is a series of images, each wrapped in a DIV tag. To improve the interface, I'm going to add the ability to mark some of the images as "new" as well as having an "active" class to identify which image is currently selected.
<div aria-hidden="true" class="gallery">
<button class="gallery-control previous hide"><</button>
<div class="viewer">
<div class="image active new">
<img class="new" src="http://www.cathmhaol.com/images/home.gif"/>
</div>
<div class="image">
<img src="http://www.cathmhaol.com/images/libraries.gif" />
</div>
<div class="image new">
<img src="http://www.cathmhaol.com/images/downloads.gif" />
</div>
<div class="image">
<img src="http://www.cathmhaol.com/images/products.gif" />
</div>
<div class="image">
<img class="new" src="http://www.cathmhaol.com/images/blog.gif" />
</div>
</div>
<button class="gallery-control next hide">></button>
</div>
<button class="gallery-control previous hide"><</button>
<div class="viewer">
<div class="image active new">
<img class="new" src="http://www.cathmhaol.com/images/home.gif"/>
</div>
<div class="image">
<img src="http://www.cathmhaol.com/images/libraries.gif" />
</div>
<div class="image new">
<img src="http://www.cathmhaol.com/images/downloads.gif" />
</div>
<div class="image">
<img src="http://www.cathmhaol.com/images/products.gif" />
</div>
<div class="image">
<img class="new" src="http://www.cathmhaol.com/images/blog.gif" />
</div>
</div>
<button class="gallery-control next hide">></button>
</div>
That's it for the markup - two control buttons and a viewer that contains our images - as many images as you want. I suppose if you wanted to get really fancy, you could attach a controller that fetches images after the user has scrolled to a certain point...but that's not covered here, not even in the JavaScript section.
CSS
Now that we have the markup settled, we're ready to add the CSS. In the CSS, I set the height of the gallery to the height of the images. In the prototype on cathmhaol.com, I'm using images that are section icons for the site - they're 200 pixel squares. Next I set the buttons to display as a block and float them to the left with a height of 100% - which makes them fill the gallery vertically - and set the width - I'm using 3em, but you can easily set them using bootstrap or foundation while also defining the "gallery" element as a row. Finally, the viewer block is floated left, as is the second control button.The viewer has a height of 100% so it fills the gallery, a white-space value of nowrap, and a width that is a multiple of image widths. In the prototype, since I'm using images that are 200 pixels wide I set the width to 600 pixels to display 3 images at one time. Once all this is done, I set the overflow-y property to hidden to make sure we don't have a vertical scrollbar and set the overflow-x property to auto - I'll switch that to hidden when I initialize the gallery using JavaScript.
Each image is contained in a DIV that is displayed as an inline-block with a height of 100%, filling the viewer. To make sure the div is at the top, I set the position to relative and top to 0. Because there's a bug – of sorts – I set the right margin to -4 pixels. If I didn't do this, there would be a 4 pixel space between the images, because I like to keep my markup clean and readable - which implies that there are other (wrong) ways to get rid of that 4 pixel space.
To identify which image is active, I use a translucent overlay with a checkmark in the middle. To do this, I use the pseudo-class "after" on the image div – .image.active:after – because you can't use the "before" or "after" pseudo-class on an IMG. In the prototype, I use a font-size of 3em and a padding-top of 30% – which you'll likely want to change to fit your specific implementation. To make sure it completely covers the image I set the width and height to 100% and moving the margin-top to -102% so there isn't an uncovered gap at the top.
To identify "new" images, I use a triangle in the upper-right corner of the image and set the content of the triangle to "new". This is a standard CSS triangle – created by using a border-width with a container that has a height and width of 0. For your implementation, you'll need to finesse the font-size, line-height, height of the border rules, and the text-indent. You may also wish to modify the content, which will also affect the values you use for each of those properties. Again, the class is on the DIV that wraps the IMG because you can't use a pseudo-class on an IMG.
Here's my code.
.gallery { height:200px; }
.gallery > button { display:block; float:left; height:100%; margin:0; padding:0; width:3em; }
.gallery > .viewer { box-shadow:inset 0 0 10px #000000; float:left; height:100%; overflow-x:auto; overflow-y:hidden; position:relative; white-space:nowrap; width:600px; }
.gallery > .viewer > .image { display:inline-block; height:100%; overflow:hidden; margin-right:-4px; position:relative; top:0; }
.gallery > .viewer > .image.active:after { background-color:rgba(61, 61, 61, 0.3); color:rgba(128, 128, 128, 0.8); content:"\2713"; display:block; font-size:3em; font-weight:bold; height:100%; margin-top:-102%; padding-top:30%; position:relative; text-align:center; width:100%; z-index:2; }
.gallery > .viewer > .image.new:before { border-right:6em solid #007bff; border-bottom:6em solid transparent; content:"NEW"; display:block; font-size:0.7em; font-weight:bold; height:0 !important; line-height:3em; margin:0; padding:0; position:absolute; text-align:right; text-indent:2.5em; top:0; right:0; width:0; z-index:1; }
.gallery > button { display:block; float:left; height:100%; margin:0; padding:0; width:3em; }
.gallery > .viewer { box-shadow:inset 0 0 10px #000000; float:left; height:100%; overflow-x:auto; overflow-y:hidden; position:relative; white-space:nowrap; width:600px; }
.gallery > .viewer > .image { display:inline-block; height:100%; overflow:hidden; margin-right:-4px; position:relative; top:0; }
.gallery > .viewer > .image.active:after { background-color:rgba(61, 61, 61, 0.3); color:rgba(128, 128, 128, 0.8); content:"\2713"; display:block; font-size:3em; font-weight:bold; height:100%; margin-top:-102%; padding-top:30%; position:relative; text-align:center; width:100%; z-index:2; }
.gallery > .viewer > .image.new:before { border-right:6em solid #007bff; border-bottom:6em solid transparent; content:"NEW"; display:block; font-size:0.7em; font-weight:bold; height:0 !important; line-height:3em; margin:0; padding:0; position:absolute; text-align:right; text-indent:2.5em; top:0; right:0; width:0; z-index:1; }
Yep, that's about 1K and it's not really minified - and if you wanted to use another method to show which image was active or didn't want to show "new" items, you could cut it down even further.
JavaScript
Now that we have the markup and the CSS complete, we add the JavaScript.First, we're going to create a handler for the buttons, so that when we click the left and right control we scroll left or right. Once you have those working, you can listen for a swipe event and trigger the left or right button click.
In the prototypes at cathmhaol.com, I don't use jQuery; however, most developers know the jQuery syntax, so to make it a little easier for those developers to understand, I created a shim that allows me to use a lot of the jQuery syntax. If you're looking through the code of the prototype on the cathmhaol.com site, feel free to consider the jQuery function roughly equivalent to jQuery's $().
In our gallery control button click event handler, I figure out whether the button clicked – which we'll call "target" – was the "next" or "previous" button and I get the viewer that is associated with the button by using the target.next() or target.prev() method.
Once I have the viewer, I find the active image – using viewer.find('.active') – and remove the 'active' class from it and add the 'active' class to the newly activated image using either the active.next() or active.prev() method. Here, I make sure we can add it to the sibling before we remove it from the active item.
Now that I've indicated to the user which item is active, I scroll the viewer to the left or right by image width – which you can get using the jQuery width method of the active image. If you're using jQuery, you'll use the scrollRight or scrollLeft method and if not, you'll either add or subtract the image width from the scrollLeft property of the viewer node – if you want details about how that works, look at the scrollLeft and scrollRight methods in the jQuery function. That concludes our button control handler.
Here's the code for the button handler.
$('.gallery-control').click(function () {
var active, current, scrollAmount, target, viewer;
target = this;
if (target.hasClass('next')) {
viewer = target.prev();
scrollAmount = 1;
} else if (target.hasClass('previous')) {
viewer = target.next();
scrollAmount = -1;
}
if (viewer) {
active = viewer.find('.active')[0];
active = active || viewer.find('.image')[0];
scrollAmount *= (active && active.width ? (active.width() || 1) : 1);
if (scrollAmount > 0) {
current = active.next().addClass('active');
} else {
current = active.prev().addClass('active');
}
if (current.node()) {
active.removeClass('active');
}
if (scrollAmount > 0) {
viewer.scrollRight(Math.abs(scrollAmount));
} else if (scrollAmount < 0) {
viewer.scrollLeft(Math.abs(scrollAmount));
}
}
});
var active, current, scrollAmount, target, viewer;
target = this;
if (target.hasClass('next')) {
viewer = target.prev();
scrollAmount = 1;
} else if (target.hasClass('previous')) {
viewer = target.next();
scrollAmount = -1;
}
if (viewer) {
active = viewer.find('.active')[0];
active = active || viewer.find('.image')[0];
scrollAmount *= (active && active.width ? (active.width() || 1) : 1);
if (scrollAmount > 0) {
current = active.next().addClass('active');
} else {
current = active.prev().addClass('active');
}
if (current.node()) {
active.removeClass('active');
}
if (scrollAmount > 0) {
viewer.scrollRight(Math.abs(scrollAmount));
} else if (scrollAmount < 0) {
viewer.scrollLeft(Math.abs(scrollAmount));
}
}
});
Finally, we remove the class that's hiding the control buttons and set the overflow-x property of the viewer to "hidden" - making the JavaScript as unobtrusive as possible and failing over to a usable interface.
$('.gallery-control.hide').removeClass('hide');
$('.gallery > .viewer').css({ 'overflow-x':'hidden' });
$('.gallery > .viewer').css({ 'overflow-x':'hidden' });
That's it – that's all there is to it.
Happy coding.
No comments:
Post a Comment