Is there a way to target the first and last element in each row using jQuery isotope?
Asked Answered
S

5

10

I have an masonry isotope grid that has n number of rows with two column sizes: 160px by 160px and 320px by 320px and I'd like to assign different styles to the first and last element of each row. My rows could have anywhere from 4 elements to 7 elements. I've been struggling with this for a bit, and wondering if it's possible.

HTML

<div id="grid" style="position: relative; overflow: hidden; height: 960px;"
class="isotope">
    <div class="one_by_one">
        <img class="thumb" src="https://s3.amazonaws.com/stitch-images/products/gucci.png"
        />
    </div>
    <div class="one_by_one">
        <img class="thumb" src="https://s3.amazonaws.com/stitch-images/products/gucci.png"
        />
    </div>
    <div class="one_by_one">
        <img class="thumb" src="https://s3.amazonaws.com/stitch-images/products/gucci.png"
        />
    </div>
    <div class="one_by_one">
        <img class="thumb" src="https://s3.amazonaws.com/stitch-images/products/gucci.png"
        />
    </div>
    <div class="one_by_one">
        <img class="thumb" src="https://s3.amazonaws.com/stitch-images/products/gucci.png"
        />
    </div>
    <div class="one_by_one">
        <img class="thumb" src="https://s3.amazonaws.com/stitch-images/products/gucci.png"
        />
    </div>
    <div class="two_by_two">
        <img class="thumb" src="https://s3.amazonaws.com/stitch-images/products/gucci.png"
        />
    </div>
    <div class="one_by_one">
        <img class="thumb" src="https://s3.amazonaws.com/stitch-images/products/gucci.png"
        />
    </div>
    <div class="one_by_two">
        <img class="thumb" src="https://s3.amazonaws.com/stitch-images/products/gucci.png"
        />
    </div>
    <div class="one_by_one">
        <img class="thumb" src="https://s3.amazonaws.com/stitch-images/products/gucci.png"
        />
    </div>
    <div class="one_by_one">
        <img class="thumb" src="https://s3.amazonaws.com/stitch-images/products/gucci.png"
        />
    </div>
    <div class="one_by_one">
        <img class="thumb" src="https://s3.amazonaws.com/stitch-images/products/gucci.png"
        />
    </div>
    <div class="two_by_two">
        <img class="thumb" src="https://s3.amazonaws.com/stitch-images/products/gucci.png"
        />
    </div>
    <div class="one_by_one">
        <img class="thumb" src="https://s3.amazonaws.com/stitch-images/products/gucci.png"
        />
    </div>
    <div class="one_by_one">
        <img class="thumb" src="https://s3.amazonaws.com/stitch-images/products/gucci.png"
        />
    </div>
    <div class="one_by_one">
        <img class="thumb" src="https://s3.amazonaws.com/stitch-images/products/gucci.png"
        />
    </div>
    <div class="one_by_two">
        <img class="thumb" src="https://s3.amazonaws.com/stitch-images/products/gucci.png"
        />
    </div>
    <div class="one_by_one">
        <img class="thumb" src="https://s3.amazonaws.com/stitch-images/products/gucci.png"
        />
    </div>
    <div class="one_by_one">
        <img class="thumb" src="https://s3.amazonaws.com/stitch-images/products/gucci.png"
        />
    </div>
    <div class="two_by_two">
        <img class="thumb" src="https://s3.amazonaws.com/stitch-images/products/gucci.png"
        />
    </div>
    <div class="one_by_one">
        <img class="thumb" src="https://s3.amazonaws.com/stitch-images/products/gucci.png"
        />
    </div>
    <div class="one_by_one">
        <img class="thumb" src="https://s3.amazonaws.com/stitch-images/products/gucci.png"
        />
    </div>
    <div class="one_by_two">
        <img class="thumb" src="https://s3.amazonaws.com/stitch-images/products/gucci.png"
        />
    </div>
    <div class="one_by_one">
        <img class="thumb" src="https://s3.amazonaws.com/stitch-images/products/gucci.png"
        />
    </div>
</div>

CSS

#grid {
    margin:auto;
    margin-top:55px;
    margin-bottom:200px;
    width:1140px
}
#grid .thumb {
    width:97%;
    height:97%
}
#grid .one_by_one {
    width:160px;
    height:160px;
    background:url(https://s3.amazonaws.com/stitch-images/assets/cell_1x1.png);
    cursor:pointer
}
#grid .one_by_two {
    width:160px;
    height:320px;
    background:url(https://s3.amazonaws.com/stitch-images/assets/cell_1x2.png);
    cursor:pointer
}
#grid .two_by_two {
    width:320px;
    height:320px;
    background:url(https://s3.amazonaws.com/stitch-images/assets/cell_2x2.png);
    cursor:pointer
}

JS

$("#grid").isotope masonry: layoutMode: 'fitRows'

View my Jsfiddle http://jsfiddle.net/TDma4/

Stewartstewed answered 27/1, 2013 at 20:40 Comment(4)
Please include some sample HTML to clarify your questionIcterus
I added code and a jsfiddleStewartstewed
Thanks. I don't think I have an answer ready for you, but adding the sample code makes your question a lot clearer :). Will respond if I do come up with an answerIcterus
Any luck with the proposed solutions?Ia
I
11

Here's one way to approach the problem.

Working Solution: http://jsbin.com/ufuleb/21/

CSS

.first, .last {
  border:2px dashed blue;
  opacity:.75;
}

JavaScript

$("#grid").isotope(
    { layoutMode : 'fitRows' });

// Last div element
$("#grid div:last-child").addClass("last");

var maxWidth = 0;

// Use max to handle last div
$("#grid div").each(function (i) {  
  matrix = matrixToArray($(this).css("-transform"));

  // identify first elements
  if ( parseInt(matrix[4],10) == 0 ) {
    $(this).addClass("first");
  }

  // identify last elements
  if ( parseInt(matrix[4],10) > parseInt(maxWidth,10) ) { 
    maxWidth = matrix[4];
  } else {      
    $(this).prev().addClass("last");
    maxWidth = 0;
  } 
});

// Util function for parsing -webkit-transform
function matrixToArray(matrix) {    
    return matrix.substr(7, matrix.length-8).split(', ');
}

Loop through each div and track the current xT value (CSS -webkit-transform). Whenever the maximum value is passed, just update the previous value which should be the last element of each row. The very last element is handled with :last-child. Note that this solution also handles the overall #grid width changing.

Example Output

Example output

This could probably be optimized further but at least provides a starting point.

I got some help from this answer: https://mcmap.net/q/323377/-get-the-value-of-webkit-transform-of-an-element-with-jquery

For reference:

Ia answered 31/1, 2013 at 19:54 Comment(2)
If you change -webkit-transform to transform, it should work in other browsers (e.g. Firefox). Also, is the last child (in DOM order) guaranteed to be the last element in the last row?Picked
Updated my code. Also, I couldn't find a test case where the last child element wasn't the element in the last row with layoutMode: 'fitRows'.Ia
P
7

Isotope has a itemPositionDataEnabled option that exposes the position of each element. Using this, along with a onLayout handler, you can compute the first and last elements of each row (demo):

$('#grid').isotope({
    itemPositionDataEnabled: true,
    onLayout: function (elems, instance) {
        var items, rows, numRows, row, prev, i;

        // gather info for each element
        items = elems.map(function () {
            var el = $(this), pos = el.data('isotope-item-position');
            return {
                x: pos.x,
                y: pos.y,
                w: el.width(),
                h: el.height(),
                el: el
            };
        });

        // first pass to find the first and last items of each row
        rows = [];
        i = {};
        items.each(function () {
            var y = this.y, r = i[y];
            if (!r) {
                r = {
                    y: y,
                    first: null,
                    last: null
                };
                rows.push(r);
                i[y] = r;
            }
            if (!r.first || this.x < r.first.x) {
                r.first = this;
            }
            if (!r.last || this.x > r.last.x) {
                r.last = this;
            }
        });
        rows.sort(function (a, b) { return a.y - b.y; });
        numRows = rows.length;

        // compare items for each row against the previous row
        for (prev = rows[0], i = 1; i < numRows; prev = row, i++) {
            row = rows[i];
            if (prev.first.x < row.first.x &&
                    prev.first.y + prev.first.h > row.y) {
                row.first = prev.first;
            }
            if (prev.last.x + prev.last.w > row.last.x + row.last.w &&
                    prev.last.y + prev.last.h > row.y) {
                row.last = prev.last;
            }
        }

        // assign classes to first and last elements
        elems.removeClass('first last');
        $.each(rows, function () {
            this.first.el.addClass('first');
            this.last.el.addClass('last');
        });
    }
});

Update: Fixed algorithm based on JSuar's feedback

Update #2: Fixed an issue when items are taller than 2 rows

Picked answered 4/2, 2013 at 14:41 Comment(2)
@Ia can you provide an example? The onLayout handler is called every time Isotope lays out the elementsPicked
It's an edge case but if you set width:520px; one of the larger elements won't be caught as a last element.Ia
K
1

Mike, If you are feeling adventurous you can extend Isotop to do this. I pulled the following from http://isotope.metafizzy.co/docs/extending-isotope.html

Any additional properties you are looking to do you can do in the .each block

_fitRowsLayout : function( $elems ) {
  var instance = this,
      containerWidth = this.element.width(),
      props = this.fitRows;

  $elems.each( function() {
    var $this = $(this),
        atomW = $this.outerWidth(true),
        atomH = $this.outerHeight(true);

    if ( props.x !== 0 && atomW + props.x > containerWidth ) {
      // if this element cannot fit in the current row
      props.x = 0;
      props.y = props.height;
    } 

    // position the atom
    instance._pushPosition( $this, props.x, props.y );

    props.height = Math.max( props.y + atomH, props.height );
    props.x += atomW;

  });
},
Kowtko answered 31/1, 2013 at 19:45 Comment(0)
S
1

use this: http://jsfiddle.net/86WVw/1/

Edit: isotope now uses translate3d, edited to use both translate n translate3d http://jsfiddle.net/86WVw/3/

additional css:

[style*="translate(0px"], [style*="translate3d(0px"], .border { // .border is class for end of row item, you can change it to whatever you want
    border: 3px dotted #f00;
}

additioan js:

gw = $('#grid').width(); // get grid width
w = $('#grid').width(); // to find smallest width, take a big number first
$('#grid>div').each(function(){ // iterate through tiles
    if ($(this).width()<w) // if width smaller then assumed smallest width
        w = $(this).width(); // change smallest width
});

var x;
for (x = w; x < gw; x += w) // iterate translated tiles from smallest width to grid width
{
    var elms = $('[style*="translate3d('+x+'px"], [style*="translate('+x+'px"]');
    $(elms).each(function(){
        if ((x+$(this).width())>(gw-w)) // if tiles left + tile width reach end of grid
        $(this).addClass('border'); // apply class
    });
}
Surplus answered 2/2, 2013 at 7:33 Comment(0)
A
0

The question is much simpler than it seems.
The size of the box is a multiple of a common matrix.

This is my approach.

var columnsWidth = 100; // the base matrix width
var columns = 8; // the number of columns available (container.width / columnsWidth)

$.each(box, function() {
    var box = $(this) // the current box
    var boxSize = [2,2] // obtained subdividing size by matrix
    var maximumXChord = (columns - boxSize[0]) * columnsWidth // the maximum X available

    if (box.x == maximumXChord) { // check if the current X Chords is equal to the maximum
        console.log('the box is the last of this row!')
    } else if (box.x == 0) { // check if the the X is 0
        console.log('the box is the first of this row!') 
    }
})

That's all!

Anselmo answered 28/1, 2014 at 21:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.