9

Please take a look at this fiddle: http://jsfiddle.net/dhcyA/

Try clicking on a block. What I want is that when the other elements disapear, the selected block will animate/ease to his giving position instead of just jumping like it does now. Then the same animation repeats itself when clicking again on the box, but then back to place.

Maybe to keep in mind:
I'm using a reponsive design, which means those blocks can be vertical and horizontal after scaling the window.

Any redevisions on the fiddle or suggustions would be great!

j08691
  • 204,283
  • 31
  • 260
  • 272
Jonathan
  • 531
  • 7
  • 21

5 Answers5

7

Here is my solution.

On your existing markup, I added a wrapper division to calculate the position of boxes inside the wrapper. Like this

<div id="wrapper">
    <div class="block">
        <h2>I'm block 1</h2>
    </div>
    ....
</div>

To maintain the fluidness of the block, I created a function to position the block on the wrapper. Here is the function for position of the blocks:

var reposition = function() {
    wrapper = $("#wrapper");
    console.log(wrapper.innerWidth());
    pLeft = 0;
    pTop = 0;
    maxRowHeight = 0;
    $(".block").each(function(){
        if($(this).data('active')) {
            $(this).data('top', pTop);
            $(this).data('left', pLeft);
        } else {
            $(this).stop(0,0).animate({
              'top' : pTop + 'px',
              'left' : pLeft + 'px'
            });
        }
            pLeft += $(this).outerWidth() + parseInt($(this).css('marginLeft'));
            if($(this).height() > maxRowHeight) maxRowHeight = $(this).outerHeight() + parseInt($(this).css('marginTop')); //Find out the longest block on the row

            if(pLeft + $(this).next().outerWidth() + parseInt($(this).next().css('marginLeft')) >= wrapper.innerWidth()) {
               pLeft = 0;
               pTop += maxRowHeight;
               maxRowHeight = 0;
            }

    });    
};

Finally, the script to toggle the block

$(".block").click(function() {

    $(this).siblings().slideToggle('slow'); //Toggle other blocks

    if(!$(this).data('active')){ //if the block is not active
        $(this).data('left', $(this).position().left); //sets its left
        $(this).data('top', $(this).position().top);   // and top position
        $(this).animate({ //animate at the top and bottom
            top:0,
            left:0
        },'slow');

        $(this).data('active',true);

    }else{

        $(this).animate({ //animate to its last known position
            top:$(this).data('top'),
            left:$(this).data('left')
        },'slow');

        $(this).data('active',false);
    }
});

Demos

  • Demo[Full] (Resize this to see the fluidness maintained)
  • Demo[Full] (version showing variable heights)

Here is what this solutions gives:

  • Remembers the last position and gradually animate to/from this position
  • Block positions are calculated and animated on load and every resize
  • Repositioning happens on $(window).resize() thus maintaining the fluid nature of the block, despite the use of position absolute
  • Support variable heights
  • Minor change on existing markup & CSS

Also fixed two issues extended by Gaby

  • Accounts for each block margin independently
  • Recalculates the position of the element after resize
Starx
  • 77,474
  • 47
  • 185
  • 261
  • Some issues with this implementation: ***a)*** If you resize the browser (*in a way that changes the layout*) after hiding the elements it gets messy. ***b)*** You do not take into account the margins of the applied class.. – Gabriele Petrioli Oct 29 '12 at 09:34
  • steps to reproduce. ***1)*** resize browser so you see 2 up and 2 down ***2)*** click on 3rd block ***3)*** resize browser (*problem 1, the visible block moves to wrong places*) ***4)*** resize browser so that the layout would have changed (*make it very wide so all would appear in 1 line*) ***5)*** re-click 3rd block (problem 2, th clicked element moves to last position - does not take into consideration the re-layout) – Gabriele Petrioli Oct 29 '12 at 14:18
  • @GabyakaG.Petrioli, Fixed both issues. Thank you for your suggestions. Any thing else you would like to comment? – Starx Oct 30 '12 at 04:53
  • it still seems to have wrong-relocation issues.. see a screencast i did at http://screencast.com/t/wciBP1mlHape – Gabriele Petrioli Oct 30 '12 at 12:09
  • 1
    I would love to hear some comments regarding the downvote to improve my answer. Please do include them – Starx Nov 02 '12 at 11:19
5

Final Update

Here is a full working solution (pretty straight forward in my opinion) with JS to set the positioning (a simple calculation) and CSS transitions for the rest..

Demo at http://jsfiddle.net/gaby/pYdKB/3/

It maintains the fluidity of float:left and works with any number of elements, and you can keep the :nth-child for the styling, and it will also work if you want to leave more than one element visible..

javascript

var wrapper = $('.wrapper'),
    boxes = wrapper.children(),
    boxWidth = boxes.first().outerWidth(true),
    boxHeight = boxes.first().outerHeight(true); 

function rePosition(){
    var w = wrapper.width(),
        breakat = Math.floor( w / boxWidth ); // calculate fluid layout, just like float:left

    boxes
        .filter(':not(.go)')
        .each(function(i){
            var matrixX = ((i)%breakat)+1,
                matrixY = Math.ceil((i+1)/breakat);

            $(this).css({
                left:(matrixX-1) * boxWidth ,
                top: (matrixY-1) * boxHeight
            });
        });
}

$('.box').click(function(){
    $(this)
        .siblings()
        .toggleClass('go');// just add the go class, and let CSS handle the rest

    rePosition(); // recalculate final positions and let CSS animate the boxes
});

$(window).resize(rePosition);
$(window).trigger('resize');

CSS

.wrapper{
    position:relative;
}

.box{
    width:200px;
    height:100px;
    position:absolute;
    margin:5px;
    cursor:pointer;
    overflow:hidden;
    text-align: center;
    line-height: 100px;

        -moz-transition-property: top,left,width,height;
     -webkit-transition-property: top,left,width,height;
         -ms-transition-property: top,left,width,height;
          -o-transition-property: top,left,width,height;
             transition-property: top,left,width,height;

        -moz-transition-duration: 1s;
     -webkit-transition-duration: 1s;
         -ms-transition-duration: 1s;
          -o-transition-duration: 1s;
             transition-duration: 1s;
}

.go{
    height:0;
    width:0;
}

note: As @Athari correctly mentioned in the comments, you should include all browser prefixes for the widest support. (my initial answer only included moz / webkit and the standard)


Original Answer

You can not do it directly with your current HTML structure. The floated concept does not support it.

But if you can afford an extra wrapper, then it is no problem..

Just slide the contents of your extra wrapper element..

Put the float code on the wrapper element and use

$(document).ready(function() {

    $(".block-wrapper").click(function() {
        $(this).siblings().find('.block').slideToggle("slow");
    });

});

Demo at http://jsfiddle.net/gaby/t8GNP/


Update #1

If you need to move the clicked element to the top left and back, then you cannot really do it with CSS.

You will need to manually position them (through JS), set CSS transitions (or jquery), and apply the new positions once you click.

Later on you might want more than one to remain visible and reposition as well..

So you might want to take a look at the great Isotope plugin which can handle this and a multitude of more situations/layouts

Gabriele Petrioli
  • 191,379
  • 34
  • 261
  • 317
  • What if the size of the boxes are different? Your final version does not work with variable box sizes – Starx Oct 29 '12 at 02:12
  • @Starx, no it does not, but the OP did not ask for that.. (*it is not too hard to extend though, just a little more heavy on calculations*) – Gabriele Petrioli Oct 29 '12 at 02:15
  • 1
    Looks nice, but what bothers me is that you've used only -moz- and -webkit- prefixes. You should always add -o- and -ms- as well. – Athari Oct 29 '12 at 02:39
  • 1
    @Athari, you're absolutely right.. it was just a speedy prototype.. But for production, one must target all browsers.. (*a handy plugin is http://leaverou.github.com/prefixfree/*) – Gabriele Petrioli Oct 29 '12 at 02:49
  • Your answer would be just fine if it was answering to the requirement: "What I want is that when the other elements disapear, the selected block will animate/ease to his giving position instead of just jumping like it does now.". - Just start animation a little bit later. – nrodic Oct 29 '12 at 21:34
  • @nrodic, yes i have not followed completely the OP requirements (*i change width as well*).. but alterations like this are easy to add, with CSS transition-delay.. [***updated version***](http://jsfiddle.net/gaby/pYdKB/4/) more close to the OP request.. – Gabriele Petrioli Oct 29 '12 at 22:02
  • @GabyakaG.Petrioli, Look at [this](http://jsfiddle.net/Starx/pYdKB/5/), even one slightly long block, breaks the effect. You didn't account for variable heights, because OP didn't asked but, nonetheless it should not break the effect too. – Starx Oct 30 '12 at 05:04
  • @Starx :) , i am not building a plugin .. i am just trying to point the OP to the right direction by presenting a solution for his problem.. (*he has provided specific CSS to work with*). Kudos to you for going the extra mile :) – Gabriele Petrioli Oct 30 '12 at 10:41
  • :) Thanks, I took the extra step because I was trying to replicate the plugin I saw somewhere. I think you might want to delete some irrelevant comments in my answer :) – Starx Oct 30 '12 at 10:57
  • If we are using jQuery, Why rely on CSS3? jQuery create cross browser solution. – mrN Nov 02 '12 at 02:03
  • 2
    @mrN : standard, hardware-accelerated, light-weight, does not depend on javascript, 3D. But a fallback (*for critical parts*) is always a good approach to designing a website.. – Gabriele Petrioli Nov 02 '12 at 10:51
3

Here is my version: http://jsfiddle.net/selbh/dhcyA/92/ (only javascript is changed, and it's responsive)

$(document).ready(function() {

    $(".block").click(function() {
        var $this = $(this);
        var pos = $this.offset();
        var $siblings = $(this).siblings().add(this);
        var marginTop = $this.css('marginTop').replace(/[^-\d\.]/g, '');
        var marginLeft = $this.css('marginLeft').replace(/[^-\d\.]/g, ''); 

        var $clone = $this.clone();

        $siblings.slideToggle("slow");

        $clone.css({
            position: 'absolute',
            left: pos.left - marginLeft,
            top: pos.top - marginTop,
            'background-color': $this.css('background-color')
        });

        $('body').append($clone);

        $this.css('opacity', 0);

        $clone.animate({
            'left': 0,
            'top': 0
        });


        $clone.click(function() {
            $siblings.slideToggle("slow", function() {
                $clone.remove();
                $this.css('opacity', 1);
            });

            $clone.animate({
                left: pos.left - marginLeft,
                top: pos.top - marginTop
            });
        });


    });

});​
Szymon Wygnański
  • 10,642
  • 6
  • 31
  • 44
  • `+1` even if there are better solutions already (if you click a button in your solution, then resize the window and click again, the button is returning to its previous position, not respecting current flow) – Michal Klouda Nov 04 '12 at 08:37
2

Great Challenge!

New Version:

Here is a much better version as it makes the blocks stay in their rows. I added a css function so that your nth-child styles could be applied even in the rows. Even maintains same HTML Structure.

Demo: http://jsfiddle.net/MadLittleMods/fDDZB/23/

The jQuery for this new revision looks like:

$('.block').on('click', function() {
    var block = $(this);

    // Keep the blocks in line
    makeRows($('body'));

    $('.block').not(this).each(function() {

        // If sibling on the same level, horizontal toggle
        // We also want ignore the toggleMethod if it is shown because we might need to reassign
        if (($(this).position().top == block.position().top && (($(this).data('toggle') == -1) || $(this).data('toggle') == null)) || ($(this).data('toggle') != -1 && $(this).data('toggleMethod') == 'side'))
        {
            $(this).data('toggleMethod', 'side');

            // Hide block
            if ($(this).data('toggle') == -1 || $(this).data('toggle') == null)
            {
                // Set properties for later use in show block
                $(this).data('overflowBefore', $(this).css('overflow'));
                $(this).css('overflow', 'hidden');
                $(this).data('marginBefore', $(this).css('margin'));

                var width = $(this).width();
                $(this).animate({
                    width: 0,
                    margin: 0
                }, function() {
                    $(this).data('toggle', width);
                });
            }
            // Show block
            else
            {
                $(this).css('overflow', $(this).data('overflowBefore'));

                $(this).animate({
                    width: $(this).data('toggle'),
                    margin: $(this).data('marginBefore')
                }, function() {
                    $(this).data('toggle', -1);
                });
            }
        }
        // Do a normal vertical toggle
        else 
        {
            $(this).data('toggleMethod', 'top');
            $(this).slideToggle('slow');
        }
    });

});

// Make rows to make the blocks in line
function makeRows(container)
{
    // Make rows so that the elements stay where they should
    var containerWidth = container.width();
    var currentRowWidth = 0;

    // Add styles first so nothing gets messed up
    container.children().each(function() {
        var itemCSS = css($(this));
        $(this).css(itemCSS);
    });

    // Now assemble the rows
    container.children().each(function() {

        var blockWidth = $(this).outerWidth() + parseInt($(this).css('margin-left')) + parseInt($(this).css('margin-right'));

        if((currentRowWidth + blockWidth) < containerWidth)
        {
            currentRowWidth += blockWidth;
        }
        else
        {
            Array.prototype.reverse.call($(this).prevUntil('.row')).wrapAll('<div class="row"></div>');
            $(this).prev().append('<div class="row_clear" style="clear: both;"></div>');

            currentRowWidth = 0;
        } 
    });    
}

// Remove the rows added
function deleteRows()
{
    var content = $('.row').contents()
    $('.row').replaceWith(content);

    $('.row_clear').remove();
}

$(window).resize(function() {
    deleteRows();
});



// Functions courtesy of marknadal
// https://stackoverflow.com/a/5830517/796832
function css(a)
{
    var sheets = document.styleSheets, o = {};
    for(var i in sheets) {
        var rules = sheets[i].rules || sheets[i].cssRules;
        for(var r in rules) {
            if(a.is(rules[r].selectorText)) {
                o = $.extend(o, css2json(rules[r].style), css2json(a.attr('style')));
            }
        }
    }
    return o;
}

function css2json(css)
{
    var s = {};
    if(!css) return s;
    if(css instanceof CSSStyleDeclaration) {
        for(var i in css) {
            if((css[i]).toLowerCase) {
                s[(css[i]).toLowerCase()] = (css[css[i]]);
            }
        }
    } else if(typeof css == "string") {
        css = css.split("; ");          
        for (var i in css) {
            var l = css[i].split(": ");
            s[l[0].toLowerCase()] = (l[1]);
        };
    }
    return s;
}

I added a makeRows and deleteRows functions so that the blocks would stay in their rows instead of getting smaller and moving into the row above. I call deleteRows whenever the window resizes so that it can maintain a responsive layout. Then if the blocks need to be toggled, I recreate the rows.

css and css2json functions are courtesy of marknadal



Old version:

I came up with a solution with .animate so that it could ease horizontally.

Here is a demo: http://jsfiddle.net/MadLittleMods/fDDZB/8/

The jQuery looks like:

$('.block').on('click', function() {
    var block = $(this);
    $(this).siblings().each(function() {
        // If sibling on the same level, horizontal toggle
        // We also want ignore the toggleMethod if it is shown because we might need to reassign
        if (($(this).position().top == block.position().top && ($(this).data('toggle') == -1) || $(this).data('toggle') == null) || ($(this).data('toggle') != -1 && $(this).data('toggleMethod') == 'side'))
        {
            $(this).data('toggleMethod', 'side');

            // Hide block
            if ($(this).data('toggle') == -1 || $(this).data('toggle') == null)
            {
                // Set properties for later use in show block
                $(this).data('overflowBefore', $(this).css('overflow'));
                $(this).css('overflow', 'hidden');
                $(this).data('marginBefore', $(this).css('margin'));

                var width = $(this).width();
                $(this).animate({
                    width: 0,
                    margin: 0
                }, function() {
                    $(this).data('toggle', width);
                });
            }
            // Show block
            else
            {
                $(this).css('overflow', $(this).data('overflowBefore'));

                $(this).animate({
                    width: $(this).data('toggle'),
                    margin: $(this).data('marginBefore')
                }, function() {
                    $(this).data('toggle', -1);
                });
            }
        }
        // Do a normal vertical toggle
        else 
        {
            $(this).data('toggleMethod', 'top');
            $(this).slideToggle('slow');
        }
    });
});​

The key was to separate the blocks that were toggled with .slideToggle and .animate because you have to apply the same when they show and hide.

Community
  • 1
  • 1
MLM
  • 3,660
  • 3
  • 41
  • 66
2

I'm kind of sleepy(It's 2:30 AM here) so I leave the half done answer here to give you an idea (I did it in 30 minutes so I guess with 30 minutes more you can get something really nice)

http://jsfiddle.net/LuL2s/2/

The trick comes by the block-holder which make the ease animation and making a difference between when they appear and disappear

JS

$(document).ready(function() {
    var open = true;
    $(".block").click(function() {
            var $this = $(this);
            var count = 0;
        if (open) {
            $this.parent().siblings().children().slideToggle("slow", function(){
                if (count++ == 2) {                 
                    $this.parent().siblings().animate({width: 'toggle', height:'toggle'});
                }
            });
        } else {
            $this.parent().siblings().animate({width: 'toggle', height:'toggle'}, function(){
                if (count++ == 2) {                 
                    $this.parent().siblings().children().slideToggle("slow");                
                }
            });

        }
        open = !open;                      
    });

});

HTML

<div class="block-holder">
    <div class="block">
        <h2>I'm block 1</h2>
    </div>
</div>

<div class="block-holder">
    <div class="block">
        <h2>I'm block 2</h2>
    </div>
</div>

<div class="block-holder">
    <div class="block">
        <h2>I'm block 3</h2>
    </div>
</div>

<div class="block-holder">
    <div class="block">
        <h2>I'm block 4</h2>
    </div>
</div>

CSS

.block {
    width: 100%;
    height: 100%;
    text-align: center;
    line-height: 100px;
    cursor: pointer;
}
.block-holder:nth-child(1) .block {
    background: green;
}
.block-holder:nth-child(2) .block {
    background: red;
}
.block-holder:nth-child(3) .block {
    background: orange;
}
.block-holder:nth-child(4) .block {
    background: pink;
}

.block-holder {
    width: 200px;
    height: 100px;
    float: left;
    margin: 20px;
}
PHP lover
  • 238
  • 2
  • 11
  • `+1` for effort, even if your fiddle doesn't work very well for button in second row (not talking about changing window size when the buttons are hidden) – Michal Klouda Nov 04 '12 at 18:58