<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Marius Marian</title>
    <description>The latest articles on Forem by Marius Marian (@marius_marian_c8c055a3fe6).</description>
    <link>https://forem.com/marius_marian_c8c055a3fe6</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3772979%2Fe2bf38ad-474f-4184-8299-2ac5b11d6574.png</url>
      <title>Forem: Marius Marian</title>
      <link>https://forem.com/marius_marian_c8c055a3fe6</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/marius_marian_c8c055a3fe6"/>
    <language>en</language>
    <item>
      <title>HTML/SVG/JAVASCRIPT</title>
      <dc:creator>Marius Marian</dc:creator>
      <pubDate>Sat, 14 Feb 2026 17:24:44 +0000</pubDate>
      <link>https://forem.com/marius_marian_c8c055a3fe6/htmlsvgjavascript-384a</link>
      <guid>https://forem.com/marius_marian_c8c055a3fe6/htmlsvgjavascript-384a</guid>
      <description>&lt;p&gt;Can someone help me with a problem? I can't do the video export function for an animation making application.&lt;/p&gt;

&lt;p&gt;import { frameManager } from "./frameManager.js";&lt;/p&gt;

&lt;p&gt;let isExporting = false;&lt;br&gt;
let overlay = null;&lt;/p&gt;

&lt;p&gt;function showOverlay(message, progress = null) {&lt;br&gt;
  if (!overlay) overlay = document.getElementById("export-overlay");&lt;br&gt;
  if (overlay) {&lt;br&gt;
    const messageP = overlay.querySelector("p") || (() =&amp;gt; {&lt;br&gt;
      const p = document.createElement("p");&lt;br&gt;
      overlay.appendChild(p);&lt;br&gt;
      return p;&lt;br&gt;
    })();&lt;br&gt;
    messageP.textContent = message;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Adaugă o bară de progres dacă nu există
let progressBar = overlay.querySelector(".export-progress");
if (progress !== null) {
  if (!progressBar) {
    progressBar = document.createElement("div");
    progressBar.className = "export-progress";
    progressBar.style.cssText = `
      width: 100%;
      height: 20px;
      background: #444;
      border-radius: 10px;
      margin-top: 15px;
      overflow: hidden;
    `;
    const fill = document.createElement("div");
    fill.className = "export-progress-fill";
    fill.style.cssText = `
      height: 100%;
      width: 0%;
      background: linear-gradient(90deg, #00ff88, #00aaff);
      transition: width 0.3s;
    `;
    progressBar.appendChild(fill);
    overlay.appendChild(progressBar);
  }
  const fill = progressBar.querySelector(".export-progress-fill");
  if (fill) fill.style.width = `${progress}%`;
}

overlay.classList.remove("hidden");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;function hideOverlay() {&lt;br&gt;
  if (overlay) overlay.classList.add("hidden");&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// ============================================&lt;br&gt;
// CAPTURE FRAME CA IMAGE - VERSIUNE CARE PĂSTREAZĂ PROPORȚIILE&lt;br&gt;
// ============================================&lt;br&gt;
function captureFrameAsImage(width, height) {&lt;br&gt;
  return new Promise((resolve, reject) =&amp;gt; {&lt;br&gt;
    const svg = document.getElementById("drawing-surface");&lt;br&gt;
    if (!svg) {&lt;br&gt;
      reject(new Error("SVG surface not found"));&lt;br&gt;
      return;&lt;br&gt;
    }&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Clonează SVG-ul
const svgClone = svg.cloneNode(true);

// ===== SCHIMBARE IMPORTANTĂ =====
// Păstrăm viewBox-ul original, nu îl forțăm
// Doar setăm width și height la dimensiunile dorite
svgClone.setAttribute("width", width);
svgClone.setAttribute("height", height);

// Lasă viewBox-ul așa cum e în original
// Asta păstrează proporțiile și pozițiile elementelor

// Serializează
let svgString = new XMLSerializer().serializeToString(svgClone);

// Adaugă namespace dacă lipsește
if (!svgString.includes('xmlns="http://www.w3.org/2000/svg"')) {
  svgString = svgString.replace('&amp;lt;svg', '&amp;lt;svg xmlns="http://www.w3.org/2000/svg"');
}

const img = new Image();
img.onload = () =&amp;gt; {
  console.log(`✅ Imagine încărcată: ${img.width}x${img.height}`);
  resolve(img);
};
img.onerror = (e) =&amp;gt; {
  console.error("Eroare încărcare SVG:", e);
  reject(new Error("Nu s-a putut încărca SVG-ul"));
};

// Codifică SVG corect pentru URL
try {
  const encoded = btoa(unescape(encodeURIComponent(svgString)));
  img.src = `data:image/svg+xml;base64,${encoded}`;
} catch (err) {
  reject(err);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;});&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// ============================================&lt;br&gt;
// FUNCȚIE DE TEST - AFLĂ DIMENSIUNILE REALE&lt;br&gt;
// ============================================&lt;br&gt;
async function testSVGDimensions() {&lt;br&gt;
  const svg = document.getElementById("drawing-surface");&lt;br&gt;
  console.log("🔍 SVG original:", {&lt;br&gt;
    width: svg.getAttribute('width'),&lt;br&gt;
    height: svg.getAttribute('height'),&lt;br&gt;
    viewBox: svg.getAttribute('viewBox'),&lt;br&gt;
    clientWidth: svg.clientWidth,&lt;br&gt;
    clientHeight: svg.clientHeight&lt;br&gt;
  });&lt;/p&gt;

&lt;p&gt;// Testează captureFrameAsImage&lt;br&gt;
  const img = await captureFrameAsImage(1920, 1080);&lt;br&gt;
  console.log("📸 Imagine generată:", {&lt;br&gt;
    width: img.width,&lt;br&gt;
    height: img.height,&lt;br&gt;
    naturalWidth: img.naturalWidth,&lt;br&gt;
    naturalHeight: img.naturalHeight&lt;br&gt;
  });&lt;/p&gt;

&lt;p&gt;// Desenează pe un canvas temporar să vezi&lt;br&gt;
  const testCanvas = document.createElement('canvas');&lt;br&gt;
  testCanvas.width = 1920;&lt;br&gt;
  testCanvas.height = 1080;&lt;br&gt;
  const ctx = testCanvas.getContext('2d');&lt;br&gt;
  ctx.drawImage(img, 0, 0, 1920, 1080);&lt;/p&gt;

&lt;p&gt;// Adaugă canvasul în pagină să vezi&lt;br&gt;
  testCanvas.style.position = 'fixed';&lt;br&gt;
  testCanvas.style.top = '10px';&lt;br&gt;
  testCanvas.style.right = '10px';&lt;br&gt;
  testCanvas.style.width = '400px';&lt;br&gt;
  testCanvas.style.border = '2px solid red';&lt;br&gt;
  document.body.appendChild(testCanvas);&lt;/p&gt;

&lt;p&gt;return "Test complet. Vezi canvasul roșu în colțul dreapta sus.";&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// ============================================&lt;br&gt;
// EXPORT VIDEO PRINCIPAL&lt;br&gt;
// ============================================&lt;br&gt;
export async function exportVideoPro({&lt;br&gt;
  width,&lt;br&gt;
  height,&lt;br&gt;
  fps = null,&lt;br&gt;
  quality = 'high'&lt;br&gt;
} = {}) {&lt;/p&gt;

&lt;p&gt;if (isExporting) {&lt;br&gt;
    alert("Exportul este deja în curs!");&lt;br&gt;
    return;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;const totalFrames = frameManager.getFrameCount();&lt;br&gt;
  if (totalFrames === 0) {&lt;br&gt;
    alert("Nu există frame-uri pentru export!");&lt;br&gt;
    return;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;const targetFPS = fps || frameManager.getFPS();&lt;/p&gt;

&lt;p&gt;try {&lt;br&gt;
    isExporting = true;&lt;br&gt;
    showOverlay(&lt;code&gt;Pregătire export ${width}x${height}...&lt;/code&gt;, 0);&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Creează canvas VIZIBIL (nu OffscreenCanvas pentru MediaRecorder)
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
canvas.style.position = 'fixed';
canvas.style.top = '-9999px';
canvas.style.left = '-9999px';
canvas.style.pointerEvents = 'none';
document.body.appendChild(canvas);

const ctx = canvas.getContext("2d", { alpha: false });
ctx.fillStyle = '#FFFFFF';
ctx.fillRect(0, 0, width, height);

showOverlay(`Capturez ${totalFrames} frame-uri...`, 5);

// Colectează frame-uri ca Images
const frames = [];
for (let i = 0; i &amp;lt; totalFrames; i++) {
  showOverlay(`Capturez frame ${i+1}/${totalFrames}`, 5 + ((i)/totalFrames) * 45);

  // Încarcă frame-ul
  await frameManager.setActiveFrame(i);

  // Capturează SVG ca Image
  const img = await captureFrameAsImage(width, height);
  frames.push(img);
}

showOverlay(`Creez video cu ${totalFrames} frame-uri la ${targetFPS} FPS...`, 50);

// Configurare MediaRecorder
const stream = canvas.captureStream(targetFPS);

// Alege cel mai bun codec disponibil
let mimeType = 'video/webm;codecs=vp9';
if (!MediaRecorder.isTypeSupported(mimeType)) {
  mimeType = 'video/webm;codecs=vp8';
}
if (!MediaRecorder.isTypeSupported(mimeType)) {
  mimeType = 'video/webm';
}

console.log("📹 Folosesc:", mimeType);

// Calculează bitrate-ul în funcție de calitate
const bitrate = quality === 'high' ? 50_000_000 : 20_000_000;

const recorder = new MediaRecorder(stream, {
  mimeType: mimeType,
  videoBitsPerSecond: bitrate
});

const chunks = [];
recorder.ondataavailable = (e) =&amp;gt; {
  if (e.data.size &amp;gt; 0) chunks.push(e.data);
};

// Pornește înregistrarea
recorder.start();

// Rulează frame-urile
for (let i = 0; i &amp;lt; frames.length; i++) {
  // Desenează frame-ul
  ctx.drawImage(frames[i], 0, 0, width, height);

  // Actualizează progres
  showOverlay(`Render frame ${i+1}/${frames.length}`, 50 + ((i+1)/frames.length) * 45);

  // Așteaptă pentru următorul frame
  await new Promise(r =&amp;gt; setTimeout(r, 1000 / targetFPS));
}

// Oprește înregistrarea
recorder.stop();

// Așteaptă să se termine procesarea
await new Promise((resolve) =&amp;gt; {
  recorder.onstop = () =&amp;gt; {
    // Creează blob-ul final
    const blob = new Blob(chunks, { type: 'video/webm' });
    const url = URL.createObjectURL(blob);

    // Curăță canvas-ul temporar
    document.body.removeChild(canvas);

    // Salvează
    const a = document.createElement("a");
    a.href = url;
    a.download = `animatie_${width}x${height}_${targetFPS}fps.webm`;
    a.click();

    showOverlay(`Finalizat! Salvare fișier...`, 100);

    setTimeout(() =&amp;gt; {
      URL.revokeObjectURL(url);
      isExporting = false;
      hideOverlay();
      alert(`✅ Export reușit!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;• Rezoluție: ${width}x${height}&lt;br&gt;
• Frame-uri: ${totalFrames}&lt;br&gt;
• FPS: ${targetFPS}&lt;br&gt;
• Calitate: ${quality}&lt;br&gt;
• Dimensiune: ${(blob.size / 1024 / 1024).toFixed(1)} MB`);&lt;br&gt;
        }, 1000);&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    resolve();
  };
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;} catch (err) {&lt;br&gt;
    console.error("❌ Eroare export:", err);&lt;br&gt;
    alert(&lt;code&gt;Eroare export: ${err.message}&lt;/code&gt;);&lt;br&gt;
    isExporting = false;&lt;br&gt;
    hideOverlay();&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// ============================================&lt;br&gt;
// INITIALIZARE&lt;br&gt;
// ============================================&lt;br&gt;
export function initExport() {&lt;br&gt;
  console.log("✅ Export module initialized");&lt;/p&gt;

&lt;p&gt;const exportBtn = document.getElementById("export-video-btn");&lt;br&gt;
  if (exportBtn) {&lt;br&gt;
    exportBtn.addEventListener("click", () =&amp;gt; {&lt;br&gt;
      // ===== NOU: Ia dimensiunea REALĂ a canvasului =====&lt;br&gt;
      const canvasElement = document.getElementById("drawing-surface");&lt;br&gt;
      const rect = canvasElement.getBoundingClientRect();&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  const width = rect.width;
  const height = rect.height;

  console.log("📏 Dimensiuni canvas REAL:", width, "x", height);

  exportVideoPro({
    width: width,
    height: height,
    quality: 'high'
  });
});
console.log("🎬 Video export button connected");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;br&gt;
}&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>help</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
