I'm trying to build a video player Web Component, but I can't seem to get the video and source elements to render properly.
customElements.define('video-player',
class extends HTMLElement {
constructor() {
super();
const template = document.getElementById('video-player-template').content;
console.log(template)
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(template.cloneNode(true));
}
}
);
<template id="video-player-template">
<video controls width="720" height="380" muted autoplay>
<slot name="video-src" />
</video>
<slot></slot>
</template>
<video-player>
<h1>Video player web component</h1>
<source slot="video-src" src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm" type="video/webm" />
</video-player>
Why doesn't the video element render?
see the documentation: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video
<video> wants a <source> element as immediate child,
thus you can't use a <slot> there.
(the same is true for <table>, which can't have <slot>)
You extract the src from your <video-player src="..."> Web Component,
then create that <source> tag.
I have added all shadowDOM styling options for a complete example
customElements.define('video-player',
class extends HTMLElement {
constructor() {
super()
.attachShadow({mode:'open'})
.append(document.getElementById(this.nodeName).content.cloneNode(true));
}
connectedCallback() {
let src = this.getAttribute("source");
let ext = src.split(".").slice(-1)[0];
this.shadowRoot
.querySelector("video")
.innerHTML = `<source src="${src}" type="video/${ext}">`;
}
}
);
<video-player source="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm">
<span slot="title">A beautiful video</span>
<div class="desc">My video description</div>
</video-player>
<video-player source="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm">
<span slot="title">Another beautiful video</span>
<div class="desc">And more description</div>
</video-player>
<template id="VIDEO-PLAYER">
<style>
:host { display:inline-block }
h1 { margin:0px;background:var(--bgcolor,green);text-align:center }
</style>
<div part="videoContainer">
<h1><slot name="title"></slot></h1>
<video controls width="100%" muted></video>
<div><slot><!-- all non-slot labeled content goes here --></slot></div>
</div>
</template>
<style>
video-player {
font: 10px Arial; /* Inheritable styles style shadowDOM */
width: 240px;
--bgcolor: gold; /* CSS properties can style shadowDOM */
}
.desc { /* container/global CSS styles slotted content!!!! */
width: 100%;
background: beige;
}
::part(videoContainer){ /* shadowParts style all usages in shadowDOM */
border: 5px solid grey;
}
</style>
ShadowDOM is styled by:
<style> within shadowDOM
Inheritable styles
https://lamplightdev.com/blog/2019/03/26/why-is-my-web-component-inheriting-styles/
(cascading) CSS properties
shadowParts (and Themes)
https://meowni.ca/posts/part-theme-explainer/
<slot> are reflected, they are NOT styled by the shadowDOM, but by its container.
See: ::slotted content
(feb 2022) Constructible StyleSheets is still a Chromium only party
https://caniuse.com/mdn-api_cssstylesheet_cssstylesheet
A slightly different approach using observedAttributes to extract setup values...
The logic is like this:
Create a basic <template id="video-player-template"> </template> tag
Re-use the component each time as a <video-player> tag
The video tag values are extracted from the <video-player>'s tag setup code.
Dynamically create a <video> tag object with extracted values from <video-player>.
Here is some testable code:
<html>
<head>
<style>
</style>
</head>
<body>
<!-- 1) create template -->
<template id="video-player-template">
<slot></slot>
</template>
<!-- 2) test as Component -->
<!-- test Component #1 with video loop -->
<video-player id="vidplayer1" width="400" height="300" muted autoplay controls loop type="video/webm"
src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm" >
</video-player>
<!-- test Component #2 -->
<video-player id="vidplayer2" width="200" height="120" muted autoplay controls type="video/webm"
src="https://www.w3schools.com/tags/movie.mp4" >
</video-player>
<!-- controller scripts -->
<script type="text/javascript">
/*
test files
> MP4: https://www.w3schools.com/tags/movie.mp4
> WEBM: https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm"
*/
var template;
var nodes;
var shadowRoot;
customElements.define(
'video-player',
class extends HTMLElement
{
constructor()
{
super();
template = document.getElementById('video-player-template').content;
console.log(template)
shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(template.cloneNode(true));
}
//# get component attributes (from template tag setup)
static get observedAttributes()
{
//# extract values from video tag template to use in output video tag
//# eg: controls id width height muted loop autoplay ... etc
return ['src', 'id', 'width', 'height', 'controls', 'muted', 'autoplay', 'loop'];
}
//# attribute change
attributeChangedCallback(property, oldValue, newValue)
{
if (oldValue === newValue) { return; }
else { this[ property ] = newValue; }
}
//# connect component
connectedCallback()
{
//# component is ready to be accessed
//# generate dynamic video tag
let player_code = "";
player_code += `<video `;
if( `${ this.id }` != "undefined")
{ player_code += `id="${ this.id }" `}
if( `${ this.width }` != "undefined")
{ player_code += `width="${ this.width }" `; }
if( `${ this.height }` != "undefined")
{ player_code += `height="${ this.height }" `; }
if( `${ this.controls }` != "undefined")
{ player_code += `controls `; }
if( `${ this.muted }` != "undefined")
{ player_code += `muted `; }
if( `${ this.autoplay }` != "undefined")
{ player_code += `autoplay `; }
if( `${ this.loop }` != "undefined")
{ player_code += `loop `; }
player_code += `<source src="${ this.src }" `;
//# get TYPE for video ( because ".type" is a reserved keyword )
if( String((`${ this.src }`).indexOf(".webm")) != -1)
{ player_code += `type="video/webm" `; }
else if( String((`${ this.src }`).indexOf(".mp4")) != -1)
{ player_code += `type="video/mp4" `; }
player_code += `/> </video> `;
//# apply code of dynamic video tag (add to page)...
this.innerHTML = player_code;
}
});
</script>
</body>
</html>
I want to record when the user press play on the youTube video that I embeded in my app using a web view.
I can't seem to find a way to get notified when the user start the video.
My webView's JSX:
<WebView
javaScriptEnabled={true}
domStorageEnabled={true}
source={{ uri: "https://www.youtube.com/embed/" + videoId }}
/>
Any pointers appreciated
For those coming here in the future I ended up using events posted from within the html that the webview picks up.
the JSX:
<WebView
style={{
height: carouselHeight,
width: width
}}
javaScriptEnabled={true}
domStorageEnabled={true}
source={{
html: youtubeHTML(videoId, width, carouselHeight)
}}
onMessage={event => {
//see ./data/youtube.js to see how this event is built
if (event.nativeEvent.data === "videoEvent_0") {
/************ do whatever you want on the event *************/
}
}}
/>
youTubeHTML():
function youtubeHTML(
videoID: string,
width: number,
height: number
) {
//see https://medium.com/capriza-engineering/communicating-between-react-native-and-the-webview-ac14b8b8b91a
const returnValue =
`<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1.0">
</head>
<body style="margin: 0px;background-color:#000;">
<!-- 1. The <iframe> (and video player) will replace this <div> tag. -->
<div id="player"></div>
<script>
// 2. This code loads the IFrame Player API code asynchronously.
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
// 3. This function creates an <iframe> (and YouTube player)
// after the API code downloads.
var player;
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
height: '` +
height +
`',
width: '` +
width +
`',
videoId: '` +
videoID +
`',
events: {
'onStateChange': onPlayerStateChange
}
});
}
// 4. The API calls this function when the player's state changes.
// The function indicates that when playing a video (state=1),
// the player should play for six seconds and then stop.
var done = false;
function onPlayerStateChange(event) {
window.postMessage("videoEvent_"+JSON.stringify(event.data))
}
</script>
</body>
</html>`;
return returnValue;
}
I’ve created a graph with echarts and want to include it to a pdf by using jspdf. I found that one way to do so might be to use canvas, transfer the graph to an image and finally include the image to the pdf. However, I fail to transfer the graph to an image. Here comes the code:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<title>Balken</title>
<script src="echarts.js"></script>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.3.4/jspdf.debug.js"></script>
<div id="body">
<div id="chart"></div>
</div>
<!-- prepare a DOM container with width and height -->
<div id="main" style="width: 750px; height: 500px"></div>
<script type="text/javascript">
// based on prepared DOM, initialize echarts instance
var myChart = echarts.init(document.getElementById('main'));
// specify chart configuration item and data
var option = {
color: ['#3398DB'],
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: [
{
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
axisTick: {
alignWithLabel: true
}
}
],
yAxis: [
{
type: 'value'
}
],
series: [
{
name: 'Salami',
type: 'bar',
barWidth: '60%',
data: [10, 52, 200, 334, 390, 330, 220]
}
]
};
// use configuration item and data specified to show chart
myChart.setOption(option);
var canvas = document.getElementById('main');
var dataURL = canvas.toDataURL();
//console.log(dataURL);
$('#exportButton').click(function () {
var pdf = new jsPDF();
pdf.addImage(dataURL, 'JPEG', 0, 0);
pdf.save('download.pdf');
});
</script>
<button id="exportButton" type="button">Export as PDF</button>
</body>
</html>
Any suggestions?
I needed this as well for a commercial product, so I did not give up until I found the solution.
You cannot use the ID of the chart to get the URL for the image, instead you need to search for the canvas.
($('canvas')[0]).toDataURL("image/png");
Notice the "[0]" means it will give your the first canvas, if you have more charts just do:
($('canvas')[0]).toDataURL("image/png");
($('canvas')[1]).toDataURL("image/png");
($('canvas')[2]).toDataURL("image/png");
3 Hours of searching and testing well spent :)
Enjoy!
I would use the toolbox, save as image:
.....
toolbox: {
feature: {
saveAsImage : {show: true}
}
}
.....
This option, among all the existing ones, will show you an icon to save the graphic as an image.
Quedaria así:
enter image description here
For more options with toolbox: http://echarts.baidu.com/echarts2/doc/option-en.html#title~toolbox
I hope it helps you.
You have to import "html2canvas" in order to make this work.
Html2canvas library will get the snapshot and that image should be written to the pdf with jspdf.
I have created a pen for this.
$("#exportButton").click(function(){
html2canvas($("#main"), {
onrendered: function(canvas) {
var dataURL=canvas.toDataURL('image/jpeg');
var pdf = new jsPDF();
pdf.addImage(dataURL, 'JPEG', 0, 0);
pdf.save("download.pdf");
}
});
});
Echart code:
<ReactEcharts
ref={(e) => {
this.echarts_react = e;
}}
option={option}
notMerge
lazyUpdate
/>
Function:
saveAsImage = (uri, name = 'undefine.jpeg') => {
var link = document.createElement('a');
link.download = name;
link.href = uri;
document.body.appendChild(link);
link.click();
};
saveAsPDF = (uri, name = 'undefine.pdf') => {
let height = echartsInstance.getHeight();
let width = echartsInstance.getWidth();
var doc = '';
if (width > height) {
doc = new jsPDF('l', 'mm', [width, height]);
} else {
doc = new jsPDF('p', 'mm', [height, width]);
}
doc.addImage(echartsInstance.getDataURL({ backgroundColor: '#fff' }), 'JPEG', 10, 10);
doc.save(name);
};
function call:
<li className="nav-item inline dropdown">
<span className="nav-link" data-toggle="dropdown">
<i className="fa fa-download" />
</span>
<div className="dropdown-menu dropdown-menu-scale pull-right">
<span
className="dropdown-item"
onClick={() =>
this.saveAsImage(this.echarts_react.getEchartsInstance().getDataURL({ backgroundColor: '#fff' }))
}>
Save as Image
</span>
<span
className="dropdown-item"
onClick={() =>
this.saveAsPDF(this.echarts_react.getEchartsInstance().getDataURL({ backgroundColor: '#fff' }))
}>
Save as PDF
</span>
</div>
</li>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.2.61/jspdf.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.5.0-beta1/html2canvas.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.5.0-beta1/html2canvas.svg.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/amstockchart/3.13.0/exporting/rgbcolor.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/canvg/1.5/canvg.min.js"></script>
<script type="text/javascript">
// $("#list1").on("click",function(){
$("#list1").click(function(){
$("#row").html(option);
var imgData;
var svgElements = $("#row").find('svg');
//replace all svgs with a temp canvas
svgElements.each(function() {
var canvas, xml;
// canvg doesn't cope very well with em font sizes so find the calculated size in pixels and replace it in the element.
$.each($(this).find('[style*=em]'), function(index, el) {
$(this).css('font-size', getStyle(el, 'font-size'));
});
canvas = document.createElement("canvas");
canvas.className = "screenShotTempCanvas";
//convert SVG into a XML string
xml = (new XMLSerializer()).serializeToString(this);
// Removing the name space as IE throws an error
xml = xml.replace(/xmlns=\"http:\/\/www\.w3\.org\/2000\/svg\"/, '');
//draw the SVG onto a canvas
canvg(canvas, xml);
$(canvas).insertAfter(this);
//hide the SVG element
////this.className = "tempHide";
$(this).attr('class', 'tempHide');
$(this).hide();
});
/* html2canvas($("#row"), {
onrendered: function(canvas) {
var imgData = canvas.toDataURL(
'image/png');
var doc = new jsPDF('p', 'mm');
doc.addImage(imgData, 'PNG', 10, 10);
doc.save('sample-file.pdf');
}
});*/
var imgData;
html2canvas($("#row"), {
useCORS: true,
'allowTaint': true,
onrendered: function (canvas) {
imgData = canvas.toDataURL(
'image/jpeg', 1.0);
canvaswidth1=canvas.width/2;
canvasheight1=canvas.height/4;
currentHeight = $("#row").height();
currentHeight2=currentHeight/2;
var imgWidth = 200;
var pageHeight = 260;
var imgHeight = canvas.height * imgWidth / canvas.width;
var heightLeft = imgHeight;
var doc = new jsPDF('p', 'm`enter code here`m','a4');
var position = 35;
doc.setFillColor(52,73,94);
doc.rect(5, 5, 200, 25, "F");
doc.setFontSize(40);
doc.setTextColor(255, 255, 255);
doc.text(80, 23, "Fitview");
doc.addImage(imgData, 'JPEG', 5, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
while (heightLeft >= 0) {
position = heightLeft - imgHeight;
doc.addPage();
doc.addImage(imgData, 'JPEG', 5, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
}
doc.save('healthcheck_Rapportage.pdf');
location.reload();
}
});
$("#row").find('.screenShotTempCanvas').remove();
$("#row").find('.tempHide').show().removeClass('tempHide');
});
</script>
I need to show Sound icon on top of the video, when sound is OFF and hide it when sound is ON. For some reason the code below is not working.
if (video.prop('muted') === true) {
video.mouseenter( function() {sound.show()} ).mouseleave( function() {sound.hide()} );
}
else {
sound.hide();
}
<video id="video" controls muted preload="none" width="446" height="250"></video>
I figured it out. Now it works like that.
video.mouseenter( function() {
if (video.prop('muted') === true) {
sound.show()
}
else {
sound.hide()
}
});
video.mouseleave( function() {
sound.hide();
});
Considering your video element:
<video id="video" controls muted preload="none" width="446" height="250">
</video>
You can determine whether sound is on by testing the volume and muted media properties of the element:
var video = document.getElementById("video");
if (video.muted || video.volume === 0) {
// Sound is off
}
This is from the Youtube API docs:
"As mentioned in the Getting started section, instead of writing an empty element on your page, which the player API's JavaScript code will then replace with an element, you could create the tag yourself."
But I cannot get the API to callback to my javascript. Any help would be great.
Here's my HTML code:
<iframe id="player" type="text/html" width="640" height="360"src="http://www.youtube.com/embed/1_QO8LoGNpc?enablejsapi=1" frameborder="0"></iframe>
JAVASCRIPT:
<script>
function onYouTubeIframeAPIReady() {
alert("hey");
player = new YT.Player('player', {
// height: '720',
// width: '1280',
// videoId: '1_QO8LoGNpc',
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
</script>
I can get it to work with using javascript creating the iframe but not setting the iframe on the page. HELP!
Function onYouTubeIframeAPIReady() should be defined before loading https://www.youtube.com/iframe_api.
Here is the idea:
<iframe id="player" type="text/html" width="640" height="360"src="http://www.youtube.com/embed/1_QO8LoGNpc?enablejsapi=1" frameborder="0"></iframe>
<script>
function onYouTubeIframeAPIReady() {
alert("hey");
player = new YT.Player('player', {
// height: '720',
// width: '1280',
// videoId: '1_QO8LoGNpc',
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
</script>
<script src="https://www.youtube.com/iframe_api"></script>
Why? Because iframe_api will call this function after being loaded. In your case iframe_api was loaded before.
Best regards.
But your code works. See it here:
http://jsfiddle.net/aXLA2/
<iframe id="player" type="text/html" width="640" height="360"src="http://www.youtube.com/embed/1_QO8LoGNpc?enablejsapi=1" frameborder="0"></iframe>
<script src="https://www.youtube.com/iframe_api"></script>