Reading from Facebook docs, it seems very easy to embed a Facebook video. However, the <div id="fb-root"></div>
must be just before the script and the fb-video
class div (from what I've observed). So if I want to embed a video I have to do something like:
<div className="card-video-wrapper">
<div id="fb-root"></div>
<script>(function(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/es_ES/sdk.js#xfbml=1&version=v2.8";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>
<div className="fb-video" data-href="my-video-url" data-show-text="false" data-autoplay="true"></div>
</div>
I first thougth that the <div id="fb-root"></div>
and the script should go just after the opening body tag, but in this way the video keeps hidden at the top of the page.
The problem comes when I want to embed multiple Facebook videos like:
<div className="card-video-wrapper">
<div id="fb-root"></div>
<script>(function(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/es_ES/sdk.js#xfbml=1&version=v2.8";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>
<div className="fb-video" data-href="my-video-url-1" data-show-text="false" data-autoplay="true"></div>
</div>
<div className="card-video-wrapper">
<div id="fb-root"></div>
<script>(function(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/es_ES/sdk.js#xfbml=1&version=v2.8";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>
<div className="fb-video" data-href="my-video-url-2" data-show-text="false" data-autoplay="true"></div>
</div>
In this case, the second one is not displayed.
Probably, <div id="fb-root"></div>
is supoposed to be placed once, but as I've said, then the video is hidden. Let's see an example:
<body>
<div id="fb-root"></div>
<script>(function(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/es_ES/sdk.js#xfbml=1&version=v2.8";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>
...
<div className="card-video-wrapper">
<div className="fb-video" data-href="my-video-url-1" data-show-text="false" data-autoplay="true"></div>
</div>
<div className="card-video-wrapper">
<div className="fb-video" data-href="my-video-url-2" data-show-text="false" data-autoplay="true"></div>
</div>
...
</body>
Inspecting with the navigator console I see these styles applied to the childs of fb-root
layer:
position: absolute;
top: -10000px;
height: 0px;
width: 0px;
What is the way to proceed to embed multiple Facebook videos?
Edit:
One would think that it has to be related to CSS. However, even removing all CSS rules I see the same problem, the videos are not shown (even just one video) when the <div id="fb-root"></div>
and the script are placed after the starting body tag.
On the other hand, I've tested it with a simple page (as @hackerrdave suggested), and it works. Now I'm wondering what could be the problem. It's a React application, using some Framework7 components (I'm not sure if this could be relevant).
The use of React is relevant, because the first DOM rendering does not contain any fb-video
element.
The problem is that the fb-video
element is rendered when the component receives the elements to render. Then, when the SDK loads, there are no videos to render. If I load the SDK once the elements are received, then the videos are shown.
Edit:
Thanks to @CBroe for the tip. A better solution is to use FB.XFBML.parse();
in the componentDidMount
and/or componentDidUpdate
function. In this way, I can put the <div id="fb-root"></div>
and the script after the body tag.
componentDidMount() {
FB.XFBML.parse();
}
componentDidUpdate() {
FB.XFBML.parse();
}
This is a workaround, because I could not find the problem exactly, but I am answering anyway since it leads to something workable. You have been warned.
I found that the Facebook SDK adds a span that somehow sits in the way. By creating your own iframe
with exactly the same src
and some other attributes, the video will display properly. If Facebook has a callback somewhere that indicates when it is done loading, we can use that to do exactly that. However, I could not find such a callback so I resolved to using MutationObserver
.
The meat of the solution is in the onFacebookLoaded
function below. Although I did not use React, I am pretty sure this should work there too. Obviously you can change which attributes you are interested in - there is allowfullscreen
amongst others, that may be of importance.
The rest of the below code only waits until Facebook has loaded the videos and stops listening for changes after that. It is written in such a way that any number of videos on the page should work.
var onFacebookLoaded = function() {
// replace .fb-video div with iframe
var oldFrame = this.querySelector('iframe'),
newFrame = document.createElement('iframe'),
attrsWeWant = new Set();
attrsWeWant.add('src').add('scrolling').add('title')
.add('width').add('height').add('frameborder');
if (oldFrame.hasAttributes()) {
var attrs = oldFrame.attributes;
for (let i = attrs.length - 1; i >= 0; --i) {
if (attrsWeWant.has(attrs[i].name)) {
newFrame.setAttribute(attrs[i].name, attrs[i].value);
}
}
}
this.parentNode.replaceChild(newFrame, this);
},
target = document.querySelectorAll('.fb-video'),
count = target.length,
observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.target.fbLoadedSeen !== true) {
onFacebookLoaded.call(mutation.target);
mutation.target.fbLoadedSeen = true;
count--;
}
});
// cleanup when all videos have loaded
if (count === 0) {
observer.disconnect();
}
});
// connect to updates
for (let i = 0; i < target.length; ++i) {
observer.observe(target.item(i), {attributes: true});
}
<div id="fb-root"></div>
<script>
(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {
return;
}
js = d.createElement(s); js.id = id;
js.src = "https://connect.facebook.net/en_US/sdk.js#xfbml=1&version=v2.8";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
</script>
<h1>Video 1</h1>
<div class="fb-video"
data-href="https://www.facebook.com/facebook/videos/10153231379946729/"
data-width="300"
data-height="300"
data-allowfullscreen="true"></div>
<h1>Video 2</h1>
<div class="fb-video"
data-href="https://www.facebook.com/facebook/videos/10153231379946729/"
data-width="500"
data-height="400"
data-allowfullscreen="true"></div>
[from other answer by OP] The problem is that the fb-video element is rendered when the component receives the elements to render. Then, when the SDK loads, there are no videos to render. If I load the SDK once the elements are received, then the videos are shown.
Yes, the SDK goes through the document and looks for elements to parse once on initialization.
But is has a method specifically for the purpose of rendering content that was added to the document after SDK initialization: FB.XFBML.parse
It can be called without any argument to go look through the whole document again, or it can be passed a DOM element, so that it will only look through a particular DOM subtree.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With