Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I make the Facebook Like button's width automatically resize?

I'm implementing the Facebook Like Button, but I'm running into some trouble with the width. I'm using the JavaScript SDK implementation, not the direct iframe.

According to the documentation, the default width is 450. That's fine, and I understand that the width can be changed by way of the width attribute on the <fb:like> tag. However, my problem is that I really cannot specify a fixed width. Due to the nature of the button, the width is not constant in all states. For example, if no one has liked the page yet it displays "Be the first of your friends to like this"; if someone has, it displays "XXX people like this. Be the first of your friends"; and still if you have liked it, it displays "You like this" or "You and XXX people like this." In other words, there are many states of the button, none of which share a constant width.

This wouldn't be much of a problem if it weren't for the fact that I want to display the button floated on the right-hand side of a <div>. To be clearer, this is what I'm doing:

<div id="wrapper">     <span class="fb-like"><fb:like show_faces="false" width="450" font="lucida grande""></fb:like></span>     ... </div> <style type="text/css"> .fblike {     display: inline-block;     padding: 0.5em;     position: absolute;     right: 0;     top: 0; } #wrapper {     position: relative; } </style> 

This works fine, but the problem is that the iframe now has a constant width of 450 pixels. Since the iframe is left-aligned, when the text is shorter there is extra space to the right. I've tried various applications of text-align: right to no avail. And the problem is further compounded by the fact that this is really just fancy markup for an iframe that is added by the FB SDK, so I'm powerless to change any of its contents with CSS or JavaScript.

I need a solution that will either (a) keep the width of the button area dynamic (i.e., it changes according to the content); or (b) right-align everything in the button area.

Thanks for any help anyone can give me!

like image 209
Josh Leitzel Avatar asked Dec 03 '10 17:12

Josh Leitzel


2 Answers

#fblike iframe {     width: 95px !important; }  #fblike .fb_edge_comment_widget iframe {     width: 330px !important; } 

And

<div id="fblike"><fb:like show-faces="false" layout="button_count"></fb:like></div> 

This way both comment and like button iframes are fixed width. No funny effects. Hope it helps.

like image 98
Andrius Paulauskas Avatar answered Oct 14 '22 16:10

Andrius Paulauskas


As everyone probably knows by now, there is no easy way to do this. I did come up with a programatic kludge of sorts, though. And when I say kludge, I really mean it! I do not consider this ready for prime time, but someone may like to tinker with the concept and try to find something workable.

The idea is that although you can't read the content width of the iframe, you can loop through a series of widths for the iframe itself until you find one that just barely prevents the text inside from wrapping. At that point, the text must touching the right-hand side of the iframe. In other words, we want to set the width of the iframe to 1px wider than the width that would cause the text to wrap.

Detecting whether the text is wrapping is easy enough in principle -- you set the iframe width, wait for the FB code to adjust the content, and then read the height. If everything fit on one line, the height should be about 25px. More than that means the text has wrapped.

The difficulty comes with the "wait for the FB code to adjust the content" part. I feel like there must be someway to force a "redraw" of the iframe, but so far I haven't found it. Calling FB.XFBML.parse() is obvious, but it doesn't seem to work. This is the part where I got stuck. I'm forcing the iframe to reload by setting its src attribute, which does the job but at a horrible price in speed. It is just meant as "proof of concept" at this point. Almost as good would be a simple way to know when any redraw was finished; I feel that should also be possible but my brain bogged down before I found anything simple.

Anyway here is some test code if you want to give it a try. It takes forever to get the button in position, but it does at least work. I left everything visible during the loading process so you can see what it's doing, on a real page it would be better to keep things hidden until everything is ready. Also note that the alignment may be slightly off because I am adjusting the width 5px at a time. If things could be made faster it would be easy to use 1px instead. Even better would probably be a rough adjustment to get close, then a fine adjustment to get it perfect. Obviously lots of experimenting to do, for whoever might want to take it up.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <script type='text/javascript'> var likediv, likeframe; function loadcode(d, s, id) {   var js, fjs = d.getElementsByTagName(s)[0];   if (d.getElementById(id)) {return;}   js = d.createElement(s); js.id = id;   js.src = "//connect.facebook.net/en_US/all.js#xfbml=1";   fjs.parentNode.insertBefore(js, fjs); } function init() { loadcode(document, 'script', 'facebook-jssdk'); likediv=document.getElementById('d2'); setTimeout(initx, 10); } function initx() { likeframe=likediv.getElementsByTagName('iframe')[0]; if (!likeframe) { setTimeout(initx, 10); return; } likeframe.style.width='225px'; setTimeout(shrink, 10); } function shrink() { likeframe=likediv.getElementsByTagName('iframe')[0]; var currwidth=parseInt(likeframe.style.width); if (currwidth>=500) return; newwidth=currwidth+5; likeframe.style.width=newwidth+'px'; likeframe.style.height='0'; likeframe.src=likeframe.src; setTimeout(checkframe, 10); } function checkframe() { var h=parseInt(likeframe.offsetHeight); if (h==0) setTimeout(checkframe, 10); else if (h>25) shrink(); //else we are done; make the frame visible if we hid it earlier } </script> </head> <body onload='init()' style='margin:10px 50px'> <div id="fb-root"></div> <div style='text-align:right'>Here is some right-aligned text to compare to.</div> <div id='d2' style='float:right;height:25px;overflow:hidden;border:1px dotted red'> <div class="fb-like" data-send="false" data-width="225" data-show-faces="false" data-action="recommend"></div> </div> </body> </html> 

EDIT: Did a little more experimenting, still an awkward workaround but faster than before:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <script type='text/javascript'> var likediv, likeframe, targwidth; function loadcode(d, s, id) {   var js, fjs = d.getElementsByTagName(s)[0];   if (d.getElementById(id)) {return;}   js = d.createElement(s); js.id = id;   js.src = "//connect.facebook.net/en_US/all.js#xfbml=1";   fjs.parentNode.insertBefore(js, fjs); } function init() { loadcode(document, 'script', 'facebook-jssdk'); likediv=document.getElementById('d2'); setTimeout(initx, 10); } function initx() { likeframe=likediv.getElementsByTagName('iframe')[0]; if (!likeframe) { setTimeout(initx, 10); return; } likeframe.style.width='225px'; setTimeout(shrink, 10); } function shrink() { likeframe=likediv.getElementsByTagName('iframe')[0]; var currwidth=parseInt(likeframe.style.width); if (currwidth>=500) return; targwidth=currwidth+5; likeframe.style.width='10px'; likeframe.style.height='0'; setTimeout(checkframe, 10); } function checkframe() { var h=parseInt(likeframe.offsetHeight); if (h==0) { setTimeout(checkframe, 10); return; } likeframe.style.width=targwidth+'px'; likeframe.style.height='0'; setTimeout(checkframe2, 10); } function checkframe2() { var h=parseInt(likeframe.offsetHeight); if (h==0) setTimeout(checkframe2, 10); else if (h>25) shrink(); //else we are done; make the frame visible if we hid it earlier } </script> </head> <body onload='init()' style='margin:10px 50px'> <div id="fb-root"></div> <div style='text-align:right'>Here is some right-aligned text to compare to.</div> <div id='d2' style='float:right;height:25px;overflow:hidden;border:1px dotted red'> <div class="fb-like" data-send="false" data-width="225" data-show-faces="false" data-action="recommend"></div> </div> </body> </html> 

FINAL EDIT: This is the best I think this method is ever going to get; the code could certainly be tweaked but it's always going to take several seconds to run through the trial widths to find what works. But it's now quick enough (around 5 seconds) that it might actually be usable. BTW I am adding each new code version rather than replacing the old ones because I haven't done much cross-browser testing of this code and it's possible the higher-speed versions won't work for someone. Since it's all experimental code I think it's better to have the different versions available for fallback.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <script type='text/javascript'> var minwidth=225, maxwidth=500, finished=false, last_was_good=null; var likediv, likeframe, targwidth, boundlow, boundhigh; function loadcode(d, s, id) {   var js, fjs = d.getElementsByTagName(s)[0];   if (d.getElementById(id)) {return;}   js = d.createElement(s); js.id = id;   js.src = "//connect.facebook.net/en_US/all.js#xfbml=1";   fjs.parentNode.insertBefore(js, fjs); } function init() { loadcode(document, 'script', 'facebook-jssdk'); likediv=document.getElementById('d2'); setTimeout(initx, 10); } function initx() { likeframe=likediv.getElementsByTagName('iframe')[0]; if (!likeframe) { setTimeout(initx, 10); return; } likeframe.style.width=minwidth+'px'; setTimeout(trynewwidth, 1); } function trynewwidth() { if (last_was_good==null) { boundlow=minwidth; boundhigh=maxwidth; } else if (last_was_good) boundhigh=targwidth; else boundlow=targwidth; finished=((boundhigh-boundlow)<2); if (finished && last_was_good) { done(); return; } if (finished && !last_was_good) targwidth=boundhigh; else targwidth=parseInt((boundlow+boundhigh)/2); setTimeout(setwidth, 1); } function done() { //All finished, if we were hiding the div make it visible now } function setwidth() { likeframe=likediv.getElementsByTagName('iframe')[0]; likeframe.style.width='10px'; likeframe.style.height='0'; setTimeout(checkframe, 10); } function checkframe() { var h=parseInt(likeframe.offsetHeight); if (h==0) { setTimeout(checkframe, 10); return; } likeframe.style.width=targwidth+'px'; likeframe.style.height='0'; setTimeout(checkframe2, 10); } function checkframe2() { var h=parseInt(likeframe.offsetHeight); if (h==0) { setTimeout(checkframe2, 10); return; } if (finished) { done(); return; } last_was_good=(h<26); setTimeout(trynewwidth, 1); } </script> </head> <body onload='init()' style='margin:10px 50px'> <div id="fb-root"></div> <div style='text-align:right'>Here is some right-aligned text to compare to.</div> <div id='d2' style='float:right;height:25px;overflow:hidden;border:1px dotted red'> <div class="fb-like" data-send="false" data-width="225" data-show-faces="false" data-action="recommend"></div> </div> </body> </html> 
like image 45
Floyd Wilburn Avatar answered Oct 14 '22 17:10

Floyd Wilburn