वेब ऐप्लिकेशन को अक्सर स्टैटिक इमेज या वीडियो रिकॉर्डिंग के तौर पर, रिच एचटीएमएल कॉन्टेंट को कैप्चर और एक्सपोर्ट करने की ज़रूरत पड़ती है. जैसे, पसंद के मुताबिक बनाए गए डैशबोर्ड, स्टाइल किए गए दस्तावेज़ या इंटरैक्टिव चार्ट. पहले, इसके लिए तीसरे पक्ष की बड़ी लाइब्रेरी की ज़रूरत होती थी. ये लाइब्रेरी, डीओएम नोड और सीएसएस प्रॉपर्टी को मैन्युअल तरीके से पार्स करती थीं, ताकि कैनवस पर विज़ुअल फ़ैक्सिमिल को फिर से बनाया जा सके. इस तरीके में कंप्यूटेशनल रिसॉर्स का ज़्यादा इस्तेमाल होता है और गड़बड़ियां होने की संभावना ज़्यादा होती है. साथ ही, यह अक्सर मॉडर्न सीएसएस लेआउट की सुविधाओं के साथ काम नहीं करता. HTML-in-Canvas API की मदद से, डेवलपर असली DOM एलिमेंट को सीधे कैनवस कॉन्टेक्स्ट में रेंडर कर सकते हैं. ब्राउज़र का नेटिव रेंडरिंग इंजन, एचटीएमएल सबट्री को पिक्सल-परफ़ेक्ट सटीक तरीके से पेंट करता है. इसलिए, इमेज या वीडियो स्ट्रीम के तौर पर सटीक विज़ुअल आउटपुट कैप्चर करना, toDataURL(), toBlob() या captureStream() जैसे पहले से मौजूद कैनवस तरीकों का इस्तेमाल करके काफ़ी असरदार होता है.
इन्हें कैसे लागू करें
देखें कि ब्राउज़र में HTML-in-Canvas की सुविधा काम करती है या नहीं:
if ('content' in HTMLCanvasElement.prototype) { // Use HTML in Canvas API } else { // Use fallback strategy }कैनवस को शुरू करें, ताकि वह डिसेंडेंट एचटीएमएल एलिमेंट को रेंडर कर सके. इसके लिए,
<canvas>एचटीएमएल एलिमेंट मेंcontent="drawable"एट्रिब्यूट जोड़ें. अपने एचटीएमएल कॉन्टेंट को<canvas>एलिमेंट के अंदर रखें. साथ ही, ड्रॉ किए जाने वाले हर एलिमेंट मेंdrawableएट्रिब्यूट जोड़ें.drawableवाला एलिमेंट, अपने सबट्री को कैप्चर करता है. हालांकि, यह नेस्ट किए गए उन डिसेंडेंट को कैप्चर नहीं करता जोdrawableभी हैं:<canvas id="canvas" content="drawable"> <div id="html-content" drawable></div> </canvas>धुंधलेपन से बचने के लिए, अपने कैनवस ग्रिड को डिवाइस के स्केल फ़ैक्टर के हिसाब से स्केल करें:
const observer = new ResizeObserver(([entry]) => { const dpc = entry.devicePixelContentBoxSize; canvas.width = dpc ? dpc[0].inlineSize : Math.round(entry.contentRect.width * window.devicePixelRatio); canvas.height = dpc ? dpc[0].blockSize : Math.round(entry.contentRect.height * window.devicePixelRatio); }); const supportsDevicePixelContentBox = typeof ResizeObserverEntry !== "undefined" && "devicePixelContentBoxSize" in ResizeObserverEntry.prototype; const options = supportsDevicePixelContentBox ? { box: "device-pixel-content-box" } : {}; observer.observe(canvas, options);canvas.onpaintइवेंट हैंडलर में, एचटीएमएल कॉन्टेंट को कैनवस पर रेंडर करें.2D कॉन्टेक्स्ट में,
drawElementImageवाला तरीका इस्तेमाल करें. यहdrawableएलिमेंट का आखिरी स्नैपशॉट और उसके ड्रॉ किए जा सकने वाले सबट्री (ड्रॉ किए जा सकने वाले अन्य चाइल्ड एलिमेंट को छोड़कर) को ड्रॉ करता है. यह एलिमेंट के बॉर्डर बॉक्स से शुरू होता है और सीएसएस ट्रांसफ़ॉर्म से पहले होता है:
canvas.onpaint = () => {
ctx.reset();
// Draw the form element at x:0, y:0
ctx.drawElementImage(form_element, 0, 0);
};
- WebGL और WebGPU कॉन्टेक्स्ट में, एलिमेंट को टेक्सचर में कॉपी किया जाता है.
canvas.captureElementImage(element)से टेक्स्चर का साइज़ तय करें. इसकेwidthऔरheight, कैनवस ग्रिड (बैकिंग स्टोर) पिक्सल में होते हैं. इसलिए, उन्हेंMath.ceil()के साथ राउंड अप करें. अगर एलिमेंट में अब तक कोई पेंट रिकॉर्ड नहीं है, तोcaptureElementImage()गड़बड़ी दिखाता है:requestPaint()को कॉल करें और अगलेpaintइवेंट पर फिर से कोशिश करें.
// Returns the element's size in canvas grid pixels, or null if the element
// has no paint record yet.
function getElementImageSize(canvas, element) {
let elementImage;
try {
elementImage = canvas.captureElementImage(element);
} catch (err) {
canvas.requestPaint(); // No paint record yet: retry on the next paint event.
return null;
}
const width = Math.ceil(elementImage.width);
const height = Math.ceil(elementImage.height);
elementImage.close();
return width > 0 && height > 0 ? { width, height } : null;
}
- WebGL कॉन्टेक्स्ट में,
texImage2Dतरीके का इस्तेमाल करके, पहले से ही टेक्सचर बैकिंग तय करें. इस गाइड में दिए गए स्निपेट, WebGL 2 कॉन्टेक्स्ट (canvas.getContext("webgl2")) का इस्तेमाल करते हैं. पिक्सल डेटा के बजायnullके साथtexImage2D()को कॉल करके, टेक्सचर स्टोरेज तय करें. इससे, बिना कोई पिक्सल अपलोड किए, दिए गए साइज़ और फ़ॉर्मैट की जीपीयू मेमोरी रिज़र्व हो जाती है. इन नियमों का पालन करें:captureElementImage()से टेक्स्चर का साइज़ तय करें, जैसा कि ऊपर दिखाया गया है.- लेवल 0 को सिर्फ़ पहली बार अपलोड करते समय या साइज़ बदलने पर असाइन करें. ऐसा इसलिए, क्योंकि फिर से असाइन करने पर टेक्सचर हट जाता है. म्यूटेबल स्टोरेज का इस्तेमाल करें: एलिमेंट का साइज़ बदलने पर, इम्यूटेबल
texStorage2D()स्टोरेज का साइज़ नहीं बदला जा सकता.
// Binds the texture, and allocates its storage on the first upload, and when
// the element size changes. `state` stores the allocated size.
function allocateElementTexture(gl, texture, state, width, height) {
gl.bindTexture(gl.TEXTURE_2D, texture);
if (state.width === width && state.height === height) return;
gl.texImage2D(
gl.TEXTURE_2D,
0, // Mipmap level
gl.RGBA8, // Internal format: 8 bits per RGBA channel
width, // Size in canvas grid pixels
height,
0, // Border: must be 0
gl.RGBA, // Format and type that match RGBA8
gl.UNSIGNED_BYTE,
null // null reserves GPU memory without uploading pixels
);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
state.width = width;
state.height = height;
}
const texture = gl.createTexture();
const textureState = { width: 0, height: 0 };
- इसके बाद, WebGL कॉन्टेक्स्ट में,
texElementSubImage2Dतरीके का इस्तेमाल करके एचटीएमएल कॉन्टेंट को टेक्सचर में रेंडर करें. इन नियमों का पालन करें:- कॉन्फ़िगरेशन में
{ width, height }पास करें. इससे कॉपी, तय किए गए टेक्सचर को भर देती है. भले ही, कैनवस को किसी भी तरह से स्केल किया गया हो. - अगर साइज़ बदल गया है, तो टेक्सचर को फिर से असाइन करें, जैसा कि ऊपर दिखाया गया है.
- कॉन्फ़िगरेशन में
// Uploads the element into the texture. Returns false if the element has no
// paint record yet. In that case, it's uploaded on the next paint event.
function uploadElementWebGL(gl, canvas, texture, element, state) {
const size = getElementImageSize(canvas, element);
if (!size) return false;
const { width, height } = size;
// Bind the texture, and reallocate it if the size changed (see above).
allocateElementTexture(gl, texture, state, width, height);
gl.texElementSubImage2D(gl.TEXTURE_2D, 0, 0, 0, element, { width, height });
return true;
}
canvas.onpaint = () => {
try {
uploadElementWebGL(gl, canvas, texture, uiElement, textureState);
} catch (err) {
console.error('texElementSubImage2D copy failed:', err);
}
};
- WebGPU के संदर्भ में,
drawElementImageToTextureतरीके का इस्तेमाल करें. इन नियमों का पालन करें:COPY_DSTऔरRENDER_ATTACHMENTके इस्तेमाल से टेक्सचर बनाएं. साथ ही, शेडर में सैंपल करने के लिएTEXTURE_BINDINGजोड़ें. गलत इस्तेमाल को WebGPU की पुष्टि करने से जुड़ी गड़बड़ी के तौर पर रिपोर्ट किया जाता है, न कि अपवाद के तौर पर.- टेक्स्चर का साइज़
sizeके तौर पर पास करें.sizeके बिना, बड़े एलिमेंट को टेक्सचर के हिसाब से क्लिप किया जाता है.sizeकी मदद से, एलिमेंट को स्केल करके फ़िट किया जाता है. - एलिमेंट का साइज़ बदलने पर, टेक्सचर को फिर से बनाएं. साथ ही, इसे रेफ़रंस के तौर पर इस्तेमाल करने वाले बाइंड ग्रुप को फिर से बनाएं.
// Create the texture once, and recreate it
// when the element size changes.
function ensureElementTexture(device, state, width, height) {
if (state.texture?.width === width && state.texture?.height === height) {
return state.texture;
}
state.texture?.destroy();
state.texture = device.createTexture({
size: { width, height },
format: "rgba8unorm",
usage:
GPUTextureUsage.TEXTURE_BINDING |
GPUTextureUsage.COPY_DST |
GPUTextureUsage.RENDER_ATTACHMENT,
});
return state.texture; // Recreate the bind groups that used the previous texture.
}
const textureState = { texture: null };
canvas.onpaint = () => {
const size = getElementImageSize(canvas, uiElement);
if (!size) return;
const previousTexture = textureState.texture;
const texture = ensureElementTexture(device, textureState, size.width, size.height);
if (texture !== previousTexture) {
// New texture: create, or rebuild, the bind groups that reference it.
}
try {
device.queue.drawElementImageToTexture({ source: uiElement }, { texture, size });
} catch (err) {
console.error('drawElementImageToTexture copy failed:', err);
}
};
सीन को रेंडर करने के लिए requestAnimationFrame लूप का इस्तेमाल करते समय, लूप में canvas.requestPaint() को कॉल करें. इससे यह पक्का किया जा सकेगा कि एचटीएमएल कॉन्टेंट, कैनवस पर रेंडर हो. event.changedElements उन एलिमेंट की सूची दिखाता है जिनकी रेंडरिंग में बदलाव हुआ है. यह पक्का करें कि अगर डिसेंडेंट एचटीएमएल एलिमेंट में कोई अपडेट हुआ है, तो सिर्फ़ एलिमेंट को फिर से ड्रॉ करें या उसके टेक्सचर को फिर से अपलोड करें:
function render() {
// Request to update the canvas
canvas.requestPaint();
requestAnimationFrame(render);
}
requestAnimationFrame(render);
canvas.onpaint = (event) => {
if (event.changedElements && event.changedElements.length > 0) {
// Redraw the element, or update its texture, as shown above
}
// In WebGL and WebGPU, render the scene, and sync the element geometry as shown in step 5
};
- एलिमेंट की ज्यामिति को सिंक करें.
ब्राउज़र को यह पता होना चाहिए कि एचटीएमएल एलिमेंट कहां बनाया गया है, ताकि उसकी डीओएम पोज़िशन, बनाए गए पिक्सल से मैच हो. उदाहरण के लिए, ताकि उपयोगकर्ता उस पर क्लिक करके टाइप कर सकें. कैनवस सिर्फ़ उन एलिमेंट का हिट टेस्ट करता है जिन्हें 2D drawElementImage() तरीके से बनाया गया है या updateElementGeometry() के साथ रजिस्टर किया गया है.
2D कॉन्टेक्स्ट के मामले में, ब्राउज़र एलिमेंट को अपने-आप सिंक करता है.
drawElementImage(), डीओएम हिट-टेस्ट की सीमाओं और स्क्रीन रीडर के फ़ोकस रिंग को ड्रॉ किए गए कोऑर्डिनेट के साथ अलाइन करता है. इसलिए, आपकोstyle.transformसेट करने की ज़रूरत नहीं है. ज्यामिति को मैन्युअल तरीके से मैनेज करने के लिए, उदाहरण के लिए, जब एक ही एलिमेंट को एक से ज़्यादा बार ड्रॉ किया जाता है, तो{ preserveElementGeometry: true }कोdrawElementImage()पर पास करें औरcanvas.updateElementGeometry()के साथ ट्रांसफ़ॉर्म लागू करें.WebGL या WebGPU के साथ 3D केस के लिए, कैनवस यह नहीं बता सकता कि एचटीएमएल एलिमेंट कहां बनाया गया है. इसलिए, आपको यह बताना होगा.
DOMMatrixकी गिनती करें, जो एलिमेंट के बॉर्डर बॉक्स को सीएसएस पिक्सल में, कैनवस पर सीएसएस पिक्सल में मैप करता है. ब्राउज़र को व्यूपोर्ट ट्रांसफ़ॉर्म का इस्तेमाल करके, 3D कोऑर्डिनेट स्पेस से सीएसएस कोऑर्डिनेट स्पेस में मैप करना होगा. इसके लिए, यह तरीका अपनाएं:- एचटीएमएल एलिमेंट को सामान्य बनाएं. एचटीएमएल एलिमेंट का साइज़, पिक्सल में तय किया जाता है. उदाहरण के लिए, 200 पिक्सल चौड़ा. हालांकि, WebGL आम तौर पर ऑब्जेक्ट को "यूनिट स्क्वेयर" के तौर पर ट्रीट करता है. उदाहरण के लिए, -0.5 से 0.5 तक. अगर आपने नॉर्मलाइज़ नहीं किया, तो आपका 200 पिक्सल का बटन 200 गुना बड़ा दिखेगा. यह चरण Y-ऐक्सिस को भी फ़्लिप करता है. ऐसा इसलिए, क्योंकि सीएसएस में, डाउन पॉज़िटिव होता है, लेकिन WebGL में, अप पॉज़िटिव होता है.
- WebGL एमवीपी मैट्रिक्स को DOM मैट्रिक्स में बदलें.
- कैनवस व्यूपोर्ट पर मैप करें. यह चरण "री-स्केलिंग" फ़ेज़ है: यह उस यूनिट-स्पेस मैथ को वापस स्ट्रेच करता है, ताकि स्क्रीन पर आपके
<canvas>एलिमेंट के सीएसएस पिक्सल डाइमेंशन से मैच किया जा सके. साथ ही, Y-ऐक्सिस को वापस फ़्लिप करता है. - फ़ाइनल ट्रांसफ़ॉर्म का हिसाब लगाएं. मैट्रिक्स को इस क्रम में गुणा करें: व्यूपोर्ट * एमवीपी * नॉर्मलाइज़ेशन. इन दोनों को एक फ़ाइनल ट्रांसफ़ॉर्म में जोड़ने से एक "मैप" बनता है. इससे ब्राउज़र को पता चलता है कि एचटीएमएल एलिमेंट को कहां रखना है, ताकि वह 3D ड्रॉइंग के साथ अलाइन हो सके. ब्राउज़र, पर्सपेक्टिव डिवाइड की प्रोसेस को पूरा करता है.
// Maps the element's border box (CSS pixels) to the canvas (CSS pixels), for a // quad that spans -0.5 to 0.5 in model space and is drawn with the `mvp` matrix. function computeCanvasTransform(canvas, element, mvp) { const width = element.offsetWidth; const height = element.offsetHeight; // 1. Normalize the HTML element (CSS pixels -> WebGL Model Space) const toGLModel = new DOMMatrix() // Scale pixels to 1 unit, flip Y (as in CSS it points down, and in WebGL it points up) .scale(1 / width, -1 / height, 1 / height) // Center the origin: (0,0) becomes (-width/2, -height/2) before scaling .translate(-width / 2, -height / 2); // 2. Convert WebGL MVP Matrix to DOM Matrix const mvpDOM = new DOMMatrix(Array.from(mvp)); // 3. Map to the canvas viewport, in CSS pixels const clipToCanvasViewport = new DOMMatrix() // Move center (0,0) to center of canvas .translate(canvas.clientWidth / 2, canvas.clientHeight / 2) // Scale normalized clip (-1..1) to viewport size, and flip Y back .scale(canvas.clientWidth / 2, -canvas.clientHeight / 2, canvas.clientHeight / 2); // 4. Multiply: (Clip -> Pixels) * (MVP) * (pixels -> unit square) return clipToCanvasViewport.multiply(mvpDOM).multiply(toGLModel); }
इसके बाद, ट्रांसफ़ॉर्म को canvas.updateElementGeometry() में canvasTransform के तौर पर पास करें. इससे एचटीएमएल एलिमेंट, रेंडर किए गए पिक्सल के ठीक ऊपर आ जाता है. इससे यह पक्का होता है कि जब कोई उपयोगकर्ता किसी बटन पर क्लिक करता है या टेक्स्ट चुनता है, तो वह असल एचटीएमएल एलिमेंट पर ही क्लिक करता है. ब्राउज़र, एलिमेंट की सुलभता सीमाओं के लिए भी ट्रांसफ़ॉर्म का इस्तेमाल करता है. उदाहरण के लिए, स्क्रीन रीडर की फ़ोकस रिंग के लिए:
canvas.updateElementGeometry(targetHTMLElement, {
canvasTransform: computeCanvasTransform(canvas, targetHTMLElement, htmlElementMVP),
});
हिट टेस्टिंग को कंट्रोल करने के लिए, इन विकल्पों का इस्तेमाल करें:
updateElementGeometry()को कॉल करने पर, एलिमेंट हिट-टेस्टिंग ऑर्डर में सबसे ऊपर चला जाता है.{ preserveHitTestOrder: true }को पास करें, ताकि वह अपनी पोज़िशन बनाए रखे.- हिट टेस्टिंग से किसी एलिमेंट को हटाने के लिए,
canvas.clearElementGeometry(element)को कॉल करें. उदाहरण के लिए, जब उसे छिपाया जाता है.
- कैनवस को एक्सपोर्ट करने के सामान्य तरीके इस्तेमाल करें. जैसे,
toDataURL(),toBlob()याcaptureStream(). एक्सपोर्ट किए गए डेटा में, रेंडर किया गया एचटीएमएल कॉन्टेंट शामिल होगा.
कोड का उदाहरण
<body>
<canvas id="canvas" style="width: 400px; height: 200px;" content="drawable">
<input id="element" drawable>
</canvas>
<button id="download">Download Image</button>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const element = document.getElementById('element');
const download = document.getElementById('download');
canvas.onpaint = (event) => {
ctx.reset();
// Draw the element into the canvas. The browser synchronizes its
// DOM position for hit testing (typing).
ctx.drawElementImage(element, 10, 10);
};
download.onclick = () => {
// Export the canvas content as an image
const dataURL = canvas.toDataURL('image/png');
const link = document.createElement('a');
link.download = 'exported-canvas.png';
link.href = dataURL;
link.click();
};
// Re-initialize canvas size on screen resize
const observer = new ResizeObserver(([entry]) => {
const dpc = entry.devicePixelContentBoxSize;
canvas.width = dpc ? dpc[0].inlineSize : Math.round(entry.contentRect.width * window.devicePixelRatio);
canvas.height = dpc ? dpc[0].blockSize : Math.round(entry.contentRect.height * window.devicePixelRatio);
canvas.requestPaint();
});
const supportsDevicePixelContentBox =
typeof ResizeObserverEntry !== 'undefined' &&
'devicePixelContentBoxSize' in ResizeObserverEntry.prototype;
const options = supportsDevicePixelContentBox ? { box: 'device-pixel-content-box' } : {};
observer.observe(canvas, options);
</script>
</body>
सबसे सही तरीके
- ज़रूरी: HTML-in-Canvas API का इस्तेमाल करने से पहले, यह देख लें कि आपका ब्राउज़र इसके साथ काम करता है या नहीं.
- ज़रूरी:
<canvas>एलिमेंट में हमेशाcontent="drawable"एट्रिब्यूट जोड़ें. - ज़रूरी: ड्रॉ किए जाने वाले हर एलिमेंट में
drawableएट्रिब्यूट जोड़ें. इसमें कैनवस के डायरेक्ट चाइल्ड एलिमेंट भी शामिल हैं. - ज़रूरी: कैनवस पर एचटीएमएल कॉन्टेंट रेंडर करने के लिए,
onpaintइवेंट हैंडलर का इस्तेमाल करें. - ज़रूरी: एचटीएमएल कॉन्टेंट को कैनवस पर रेंडर करने के लिए, 2D में
drawElementImageतरीके का इस्तेमाल करें या WebGPU मेंdrawElementImageToTextureका इस्तेमाल करें. - ज़रूरी: WebGL में,
texImage2D()के साथ टेक्सचर बफ़र को पहले से तय करें. ऐसा सिर्फ़ एक बार करें (जब इसे शुरू किया जाए या एलिमेंट का साइज़ बदला जाए). टेक्सचर को सिर्फ़ तब फिर से असाइन करें, जब साइज़ में बदलाव हो. साथ ही,TEXTURE_MIN_FILTERकोLINEARपर सेट करें. - ज़रूरी: WebGL में, एचटीएमएल कॉन्टेंट को टेक्सचर में रेंडर करने के लिए,
texElementSubImage2Dतरीके का इस्तेमाल करें. इसके लिए, पहले से तय किए गएtexImage2D()का इस्तेमाल करें. इसका साइज़captureElementImage()से तय करें. - ज़रूरी: WebGPU में, टेक्सचर का साइज़
captureElementImage()से तय करें. साथ ही, इसे सिर्फ़ तब फिर से बनाएं, जब साइज़ में बदलाव हो.COPY_DSTऔरRENDER_ATTACHMENTके इस्तेमाल से टेक्सचर बनाएं. साथ ही, टेक्सचर का साइज़sizeके तौर पर पास करें. - ज़रूरी: एचटीएमएल एलिमेंट की ज्यामिति को रेंडर किए गए कॉन्टेंट के साथ सिंक करें. 2D में,
drawElementImageयह काम अपने-आप करता है. WebGL और WebGPU में,canvas.updateElementGeometry(element, { canvasTransform })को कॉल करें. - ज़रूरी: स्क्रीन का साइज़ देखने के लिए,
ResizeObserverका इस्तेमाल करें. साथ ही, कैनवस के साइज़ को डिवाइस के पिक्सल के हिसाब से अपडेट करें. - कैनवस में क्रॉस-ऑरिजिन कॉन्टेंट को एंबेड न करें, क्योंकि यह काम नहीं करता.
ResizeObserverकोonpaintइवेंट हैंडलर में शुरू न करें, क्योंकि इससे मेमोरी लीक हो सकती है.
फ़ॉलबैक रणनीतियां
फ़िलहाल, HTML-in-Canvas API सभी मॉडर्न ब्राउज़र के साथ काम नहीं करता. इसलिए, आम तौर पर फ़ॉलबैक रणनीति की ज़रूरत होती है. हालांकि, इस एपीआई की बेहतर परफ़ॉर्मेंस को देखते हुए, अगर ब्राउज़र पर HTML-in-Canvas काम करता है, तो इसका इस्तेमाल किया जाना चाहिए.
अगर आपको कैनवस से एचटीएमएल कॉन्टेंट एक्सपोर्ट करना है, तो html2canvas, dom-to-image या snapdom जैसी लाइब्रेरी का इस्तेमाल करें.
एचटीएमएल इंटरैक्शन को फ़्रेम दर फ़्रेम कैप्चर करने के लिए, rrweb जैसी लाइब्रेरी का इस्तेमाल करके डीओएम म्यूटेशन कैप्चर करें. उदाहरण के लिए, स्ट्रीमिंग के लिए.
इसके अलावा, चेतावनी देने वाला एक मैसेज दिखाएं. इसमें बताएं कि ब्राउज़र में एचटीएमएल मीडिया एक्सपोर्ट करने की सुविधा काम नहीं करती, क्योंकि यह HTML-in-Canvas के साथ काम नहीं करता.