SitePoint Sponsor

User Tag List

Results 1 to 20 of 20
  1. #1
    SitePoint Zealot
    Join Date
    Dec 2008
    Posts
    110
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)

    Add equal amount of padding to anchors in a fluid horizontal list

    I've spent quite a bit of time looking at this, and I think it's probably not possible with CSS. But if someone can prove me wrong, that would be great!

    What I'm looking to do is to have a horizontal navigation menu where there is an equal spacing between each item, but each item may be a different size. The menu needs to be fluid, so that the gap between each item reduces / increases as the page width reduces / increases.

    The justify trick detailed here: http://stackoverflow.com/questions/6...ly-spaced-divs is along the right lines. But when I say an equal spacing between the menu items, what I really mean is that I want equal padding applied to the anchors that make up the list. There should be no actual spacing between the list items.




    HTML Code:
    <ul>
    	<li><a href="#">Option One</a></li>
    	<li><a href="#">Option Two</a></li>
    	<li><a href="#">This is option three</a></li>
    	<li><a href="#">Opt 4</a></li>
    	<li><a href="#">Option 5</a></li>
    </ul>
    The nearest I've been able to get so far is using the CSS3 calc function, but this requires knowing the number of items in the menu, and the width of each item, which is not really realistic. http://jsfiddle.net/TN9hf/2/embedded/result/
    Code:
    ul{
    	list-style-type: none;
      width: 80%;
    }
    li{
    	display: inline-block;
        text-align: center;
        padding: 0;
        margin: 0;
        font-size: 105%;
    }
    /*combined width of list items with no padding = 79+161+86+60+135=521px*/
    li:nth-child(1){
    	width: calc((100% - 521px)/5 + 79px);
    }
    li:nth-child(2){
    	width: calc((100% - 521px)/5 + 161px);
    }
    li:nth-child(3){
    	width: calc((100% - 521px)/5 + 86px);
    }
    li:nth-child(4){
    	width: calc((100% - 521px)/5 + 60px);
    }
    li:nth-child(5){
    	width: calc((100% - 521px)/5 + 135px);
    }
    li a{
        display: block;
        padding: 5px 0;
        background: #999;
    }
    Attached Images Attached Images

  2. #2
    SitePoint Mentor bronze trophy
    ronpat's Avatar
    Join Date
    Jun 2012
    Location
    NJ, USA
    Posts
    2,466
    Mentioned
    61 Post(s)
    Tagged
    2 Thread(s)
    I believe that the answer is that it can be done with JavaScript but not with CSS. I don't know how to write the JavaScript, but the concept seems pretty simple. @Paul O'B is around today. He could provide a definitive answer.

  3. #3
    The CSS Clinic is open silver trophybronze trophy
    Paul O'B's Avatar
    Join Date
    Jan 2003
    Location
    Hampshire UK
    Posts
    40,288
    Mentioned
    179 Post(s)
    Tagged
    6 Thread(s)
    Hi,

    I don't think it is possible in an automatic way based on the criteria that you need,

    The justify trick is the way I would have tackled this and indeed the method was first discovered in the Sitepoint forums in a quiz I set some years ago.

    However it seems that you don't want space but that you want each element to butt up to the next. You can get close using display:table and table-cell but it only equalises the cells and not based on the content. If you use table-layout:fixed then the cells are equal but in the table-layout:auto mode you get more spaced out elements but not an exact match for each unfortunately. I also thought that flexbox was supposed to do this but on testing it seems that it also equally distributes the items based on a set width for each.

    e.g.
    Code:
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Untitled Document</title>
    <style type="text/css">
    
    ul{
      display: -moz-flex;
     -moz-justify-content:space-between;
    	display: -webkit-flex;
     -webkit-justify-content: space-between;
    	justify-content:space-between;
    	display:flex;
    	list-style:none;
    	margin:0 auto;
    	padding:0;
    	width:80%;
    	border:1px solid #000;
    }
    ul li {
    	line-height:30px;
    	-webkit-flex: 1;
    	-moz-flex: 1;
    	flex: 1; 
    	text-align:center;
    	width:100%;
    }
    ul li a{display:block;background:blue;color:red}
    a:hover { background:green }
    
    
    
    </style>
    </head>
    
    <body>
    <ul>
    		<li><a href="#">Option One</a></li>
    		<li><a href="#">Option Two</a></li>
    		<li><a href="#">This is option three</a></li>
    		<li><a href="#">Opt 4</a></li>
    		<li class="last"><a href="#">Option 5</a></li>
    </ul>
    
    </body>
    </html>
    There may well be a setting that will satisfy the requirements but flexbox is still buggy and not widely supported.

    I think the best you can hope for with automatic sizing is using display:table and display table-cell but they don't produce evenly spaced elements based on content.
    Code:
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Untitled Document</title>
    <style type="text/css">
    ul {
    	display:table;
    	list-style:none;
    	margin:0 auto;
    	padding:0;
    	width:80%;
    	border:1px solid #000;
    }
    ul li {
    	line-height:30px;
    	display:table-cell;
    	text-align:center;
    }
    ul li a {
    	display:block;
    	background:blue;
    	color:red;
    	border-left:1px solid #000;
    	border-right:1px solid #000;
    }
    a:hover { background:green }
    </style>
    </head>
    
    <body>
    <ul>
    		<li><a href="#">This is option One</a></li>
    		<li><a href="#">Option Two</a></li>
    		<li><a href="#">This is option three</a></li>
    		<li><a href="#">Opt 4</a></li>
    		<li class="last"><a href="#">Option 5</a></li>
    </ul>
    </body>
    </html>
    Of course that just renders larger text lines with larger space so is not equal as such and the alternative table-layout:fixed method just makes all cells the exact same width.

  4. #4
    Community Advisor silver trophybronze trophy
    dresden_phoenix's Avatar
    Join Date
    Jun 2008
    Location
    Madison, WI
    Posts
    2,791
    Mentioned
    34 Post(s)
    Tagged
    2 Thread(s)
    You may be able to achieve this using flex-box, but that wouldn't make your design very cross browser friendly (then again , neither is calc(); ) otherwise i believe you will need .js


    UPDATE:

    Paul ninja'd me and with a better answer. The true issue here is we are not really targeting the element, but the space between.

  5. #5
    SitePoint Zealot
    Join Date
    Dec 2008
    Posts
    110
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    Thanks for the replies everyone. I think that using js with table-cell display for non-js users is going to be the solution.

  6. #6
    SitePoint Mentor bronze trophy
    ronpat's Avatar
    Join Date
    Jun 2012
    Location
    NJ, USA
    Posts
    2,466
    Mentioned
    61 Post(s)
    Tagged
    2 Thread(s)
    Quote Originally Posted by djeyewater View Post
    Thanks for the replies everyone. I think that using js with table-cell display for non-js users is going to be the solution.
    Yes, that's exactly the solution I was expecting. If you already have the JavaScript, I would be grateful if you would post it. Otherwise, I intend to try to write it (or get some help doing so) because this is a repeated request and I'd like to have the solution "at the ready".

    Cheers

  7. #7
    SitePoint Zealot
    Join Date
    Dec 2008
    Posts
    110
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    I haven't written the js yet, but I'll post it here when done.

  8. #8
    SitePoint Mentor bronze trophy
    ronpat's Avatar
    Join Date
    Jun 2012
    Location
    NJ, USA
    Posts
    2,466
    Mentioned
    61 Post(s)
    Tagged
    2 Thread(s)
    Thanks, I'll keep an eye open for it.

  9. #9
    The CSS Clinic is open silver trophybronze trophy
    Paul O'B's Avatar
    Join Date
    Jan 2003
    Location
    Hampshire UK
    Posts
    40,288
    Mentioned
    179 Post(s)
    Tagged
    6 Thread(s)
    Quote Originally Posted by ronpat View Post
    Thanks, I'll keep an eye open for it.
    Meanwhile feast your eyes on this version. It's pure CSS and automatically justifies depending on the text length while also stretching the target area to the edges.

    It works by using the span as an inline-block which gets justified but still leaves the anchor element as an inline element. Some generated content is then added to the anchor causing the inline anchor element to also justify and spread out to the full width. The whitespace gaps have to be killed by butting the tags together. It's a tricky combination and needs a couple of non breaking spaces to trigger the justify behaviour. (It may be possible to remove the non breaking spaces and use generated content instead but I couldn't get it working in the short time I had.

    Unfortunately it only really works in Firefox although I have had IE and chrome working separately at times with variations of the code. It's too buggy and tricky to use for real though unless it can be packaged more neatly.

    Useful as an exercise in what can be done though

  10. #10
    SitePoint Zealot
    Join Date
    Dec 2008
    Posts
    110
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    Here's the js I promised. I'm afraid that it's tailored to my use, e.g. I have a bit in there that modifies some css specific to how my menu is styled. It also won't work if your ul or lis have padding or margin applied. So I'm not sure how useful it will be to anyone. But at least it should provide a starting point if you want a similar function.

    It doesn't work in IE6 or 7, you could add a conditional statement to set the display to inline instead of inline-block in IE if you wanted support for those browsers. Sometimes in IE the end item drops off the end of the menu onto the next line, but I haven't had time to debug this yet.

    Since the site I'm doing this for doesn't really need jQuery, I decided to do it in raw js, which means I needed a couple of helper functions. So I stole Craig Buckler's each function from his series of articles on Native JavaScript Equivalents of jQuery Methods.

    I might try and write a more universal function with an example page sometime, but unfortunately I don't have the time at the moment.

    Code:
    (function(){
    
    /**loop through an array / list, passing each item in the list to the callback function
     *The callback function receives two arguments, the current item in the list, and the index of that item in the list
     *If the callback function returns false, then the loop will exit
     *(Taken from http://www.sitepoint.com/jquery-vs-raw-javascript-3-events-ajax/)
     *@param obj The array or nodeList to loop through
     *@param fn The callback function that each item in the list will be passed
     */
    function Each(obj, fn) {
    	if (obj.length) for (var i = 0, ol = obj.length, v = obj[0]; i < ol && fn(v, i) !== false; v = obj[++i]);
    	else for (var p in obj) if (fn(obj[p], p) === false) break;
    };
    
    /**
     *Cross-browser method to attach an event listener
     *@param HTMLObject el The element to attach the event listener to
     *@param string eventType The type of event to listen for
     *@param function fn The function to fire when the event occurs
     */
    function addEvent(el, eventType, fn) {
        if (el.addEventListener) {
            return el.addEventListener(eventType, fn);
        }
        else if (el.attachEvent) {
            return el.attachEvent('on'+eventType, fn);
        }
    }
    
    /**
     *Space items equally across a horizontal menu so that they fill the whole menu by applying
     *the same amount of padding to the anchor element contained in each list item.
     *@param HTMLObject The ul or ol html object that is the menu
     *@param number The minimum amount of padding to apply
     */
    function justifyMenu(menu,minPadding) {
        //check we have an ol or ul menu
        if (menu.tagName != 'UL' && menu.tagName != 'OL') {
            throw new TypeError('menu should be an unordered or ordered list HTML Object');
        }
        //initialise some variables
        var lis = menu.children,
        as=[],
        contentWidth=0,
        menuWidth;
        //set minPadding to a default value of 5 if not provided
        minPadding = (typeof minPadding == 'number') ? minPadding : 5;
        //set the list items to wrap to their contents and get their width
        Each(lis, function(as){return function(el){
            el.style.display='inline-block';
            //for IE7 set display to inline and add zoom: 1
            
            //get the child anchor tag
            Each(el.children, function(as){return function(el){
                if (el.tagName == 'A') {
                    //reset the anchor padding to the minimum
                    el.style.paddingLeft=minPadding+'px';
                    el.style.paddingRight=minPadding+'px';
                    //add the anchor to the array of anchors to have their padding modified
                    as.push(el);
                }
            }; }(as))
            //now we can get the width of the list item
            contentWidth+=el.offsetWidth;
            
        };}(as));
        //only justify the menu items if they are smaller with the minimum padding applied than the menu width
        if (contentWidth < (menuWidth=menu.offsetWidth)) {
            //Taking the contentWidth away from the menuWidth gives us the total spare space
            //Dividing the spare space by the number of list items gives us the total padding to apply to each item
            //Dividing this by 2 gives us the amount of padding to apply left and right
            //We need to round the value down otherwise IE9 & 10 will suffer from rounding issues, pushing the final list item off the end and onto a new row
            var padding=Math.floor((menuWidth-contentWidth)/(lis.length*2))+minPadding;
            Each(as, function(el){
                el.style.paddingLeft=padding + 'px';
                el.style.paddingRight=padding + 'px';
            });
            //Fix the display of the submenus now we are using inline-block display for the parents instead of table-cell (see ".main-navigation li ul" ruleset in theme's style.css)
            Each( menu.getElementsByTagName('ul'), function(el){
                var s=el.style;
                s.position='absolute';
                s.top='100%';
                s.zIndex = 1;
                s.height = 'auto';
            });
        }
    }
    
    //don't justify the menu for IE7
    if(document.getElementsByTagName('html')[0].className.indexOf('ie7') == -1){
        justifyMenu(document.getElementById('myMenu'));
        addEvent(window, "resize", function(e) {
            justifyMenu(document.getElementById('myMenu'));
        });
    }
    
    
    
    })();
    Last edited by djeyewater; Jul 24, 2013 at 11:23. Reason: 500 Internal Server Error

  11. #11
    SitePoint Mentor bronze trophy
    ronpat's Avatar
    Join Date
    Jun 2012
    Location
    NJ, USA
    Posts
    2,466
    Mentioned
    61 Post(s)
    Tagged
    2 Thread(s)
    Quote Originally Posted by Paul O'B View Post
    Meanwhile feast your eyes on this version. It's pure CSS and automatically justifies depending on the text length while also stretching the target area to the edges.

    It works by using the span as an inline-block which gets justified but still leaves the anchor element as an inline element. Some generated content is then added to the anchor causing the inline anchor element to also justify and spread out to the full width. The whitespace gaps have to be killed by butting the tags together. It's a tricky combination and needs a couple of non breaking spaces to trigger the justify behaviour. (It may be possible to remove the non breaking spaces and use generated content instead but I couldn't get it working in the short time I had.

    Unfortunately it only really works in Firefox although I have had IE and chrome working separately at times with variations of the code. It's too buggy and tricky to use for real though unless it can be packaged more neatly.

    Useful as an exercise in what can be done though
    This is very cool, Paul. Kinda mind boggling. Is the flaw between browsers due to a lack of standards compliance or is this "uncharted territory"? I learned through experience a couple of weeks ago that just because something works nicely in Firefox doesn't mean that it's standards compliant. Quite the opposite, in fact. That's why I wonder what is causing the disparity between browsers. It's an amazing feat even if it's "edgy".

  12. #12
    SitePoint Mentor bronze trophy
    ronpat's Avatar
    Join Date
    Jun 2012
    Location
    NJ, USA
    Posts
    2,466
    Mentioned
    61 Post(s)
    Tagged
    2 Thread(s)
    Quote Originally Posted by djeyewater View Post
    Here's the js I promised. I'm afraid that it's tailored to my use, e.g. I have a bit in there that modifies some css specific to how my menu is styled. I might try and write a more universal function with an example page sometime, but unfortunately I don't have the time at the moment.
    Your JavaScript is far more sophisticated than my simplistic vision was/is. With a little more refining, yours could probably become nearly plug-n-play in modern browsers. Very impressive. If I can get my simple version to work, I'll post it for its entertainment value; but so far, it's a no-go. I'm still trying to get yours to work, actually.

    Thanks very much for posting this. I appreciate the skill that you are demonstrating.

  13. #13
    SitePoint Zealot
    Join Date
    Dec 2008
    Posts
    110
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    When I have a bit more time I'll make up an example page, as my js probably doesn't make a lot of sense without the html and css. Unfortunately I can't easily just pull out the relevant html and css from the project I'm using this for as it's a wordpress child theme, so there are some CSS rules over-riding the parent theme CSS rules and gets a bit tricky trying to figure it all out.

    Paul's example looks promising, on the top menu the last option drops off (well the anchor stays but gets small and the text disappears) in FF 22 for me, but the others are okay. I'll have to take a proper look at it later. Thanks for posting it Paul!

  14. #14
    The CSS Clinic is open silver trophybronze trophy
    Paul O'B's Avatar
    Join Date
    Jan 2003
    Location
    Hampshire UK
    Posts
    40,288
    Mentioned
    179 Post(s)
    Tagged
    6 Thread(s)
    Quote Originally Posted by djeyewater View Post
    Paul's example looks promising, I'll have to take a proper look at it later. Thanks for posting it Paul!
    It's not really viable for production in that state so don't spend too long on it. I'll need to revisit it and see if I can make it more stable but I thin we're at the limits of what we can do here with css alone.

    Quote Originally Posted by ronpat
    Is the flaw between browsers due to a lack of standards compliance or is this "uncharted territory"?
    The browsers are pretty consistent with the demo except for the start and end positions of the menu. The menu works fine as a justified menu but like justified text the first word and last word would be at the end of the line. The hacks come into play when trying to move the first and last item away from the edge to match the rest. Theoretically an invisible character at the start and end of the line should give the effect needed and indeed does work in Firefox but other browsers don't do it exactly and need more characters. There may be an optimum to suit all browsers but needs more testing.

  15. #15
    SitePoint Zealot
    Join Date
    Dec 2008
    Posts
    110
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    I've had a bit of time to get a test page up with the javascript now: http://www.xoogu.com/files/2013/07/j...-of-mu-mu.html

    There are still quite a few things I need to work out, and I've only tested this on Firefox (it's modified from what I used for the site I was on working on). But at least you should be able to see it in action.

    Strangely, when the total width of the list items exactly match the space available, firefox pushes the last item onto a new line:
    Screenshot from 2013-07-27 09:15:04.png

  16. #16
    The CSS Clinic is open silver trophybronze trophy
    Paul O'B's Avatar
    Join Date
    Jan 2003
    Location
    Hampshire UK
    Posts
    40,288
    Mentioned
    179 Post(s)
    Tagged
    6 Thread(s)
    Quote Originally Posted by djeyewater View Post
    Strangely, when the total width of the list items exactly match the space available, firefox pushes the last item onto a new line:
    Screenshot from 2013-07-27 09:15:04.png

    You could just add a negative right margin of 2px to the last list item to stop it dropping.

    e.g.
    Code:
    ul li:last-child{margin-right:-2px}

  17. #17
    SitePoint Zealot
    Join Date
    Dec 2008
    Posts
    110
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    I've done a bit more work on this now, and it should work in most browsers, but there are still a few issues. Same link as before: Justified menu example

    The submenus are messed up in IE 7 (and won't work in IE6). Rather than trying to fix it, I have just disabled them in these browsers.

    It doesn't work in IE6. If you need IE6 support, it can be modified by setting the anchors' display to inline and zoom to 1, e.g.

    Code:
    //get the child anchor tag
                Each(el.children, function(as){return function(el){
                    if (el.tagName == 'A') {
                        //reset the anchor padding to the minimum
                        el.style.paddingLeft=minPadding+'px';
                        el.style.paddingRight=minPadding+'px';
                        if(IE==6){
                            el.style.display='inline';
                            el.style.zoom=1;
                        }
                        //add the anchor to the array of anchors to have their padding modified
                        as.push(el);
                    }
                }; }(as))
    With javascript disabled I have had to use whitespace: nowrap to prevent the text in each menu item from wrapping. If the text wraps onto a new line, then the item has a larger height than the other items. I have tried to fix this, but have not been able to find a solution.



    The sub menus fit to the width of their parent. Ideally they would expand their contents so that the text does not wrap (or overflow). However, I have not been able to find a solution for this either.



    I haven't looked at Paul's idea or flexbox properly yet.
    Attached Images Attached Images

  18. #18
    SitePoint Zealot
    Join Date
    Dec 2008
    Posts
    110
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    I've had a look at Flex box and Paul's example, refined the javascript solution, and written it all up into a blog post: http://www.xoogu.com/2013/justified-...ipt-solutions/

    Hope it helps someone. If anyone wants to propose an alternative solution or point out mistakes I've made / things that could be done better, that would be great.

  19. #19
    The CSS Clinic is open silver trophybronze trophy
    Paul O'B's Avatar
    Join Date
    Jan 2003
    Location
    Hampshire UK
    Posts
    40,288
    Mentioned
    179 Post(s)
    Tagged
    6 Thread(s)
    Quote Originally Posted by djeyewater View Post
    I've had a look at Flex box and Paul's example, refined the javascript solution, and written it all up into a blog post: http://www.xoogu.com/2013/justified-...ipt-solutions/

    Hope it helps someone. If anyone wants to propose an alternative solution or point out mistakes I've made / things that could be done better, that would be great.
    Good write up. Well done I'm sure it will help a few people.

    As an aside I see you mention IE10 not working with flexbox and the reason is that IE10 uses the old syntax and IE11 uses the new syntax.

  20. #20
    SitePoint Zealot
    Join Date
    Dec 2008
    Posts
    110
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    Thanks for the tip Paul. I've added an update to the article now.

    With the correct flexbox syntax for IE10, it renders the menu the same way as a fixed layout table, which is different to Chrome and Firefox.


Tags for this Thread

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •