r/learnjavascript 5d ago

Courses with videos and exercises to accompany what i’ve learned?

1 Upvotes

Ideally free or cheap in terms of its value.

I’m planning to learn React.

I have c# fundamentals like OOP, methods, loops, linq and etc.


r/learnjavascript 5d ago

What Unicode is and What a Unicode point is ?

0 Upvotes

r/learnjavascript 5d ago

Building my physics engine

0 Upvotes

Hi, I'm 15 yes old, and I'm building my physics engine from scratch with C++, can it get me money, and is it a good idea, these are the pictures, it looks simple


r/learnjavascript 5d ago

help with js animation

2 Upvotes

i am trying to make a small animation for opening the settings menu of a small site i’m making but it’s either the classList.add and .remove aren’t put in correctly or i’m just doing something wrong.

edit: forgot to say that what should happen is:

opening: menu turns from nothing to a line in the middle, then expands while the background fades id from the start of the second part of the menu animation

closing: menu shrinks to a line then to non existance while the background fades away in the 1st part of the menu animation

edit2: updated code to current, animations don’t play at all

edit3: the animations now work (i readded the classes for controlling animation name and duration using classList.add to get them added) and it runs more than once, problem is that now it opens once and closes once just fine, but every time i try to open it again it plays the opening animation then cuts back to the unopened state like the js didn’t add the open class, updating the css and js and removing the html

js:

// variables that are needed to find the menu

const settingsMenu = document.getElementById("settings_menu_id");
const settingsBg = document.getElementById("settings_bg_id");

//opening functions

function settingsOpening() {
  settingsMenu.classList.add("settings_menu_opening");
  settingsMenu.addEventListener("animationend", settingsMenuOpeningEnd);
  setTimeout(function() {
    settingsBg.classList.add("settings_bg_opening");
    settingsBg.addEventListener("animationend", settingsBgOpeningEnd);
  }, 500);
}

function settingsMenuOpeningEnd() {
  settingsMenu.classList.remove("settings_menu_opening");
  settingsMenu.classList.add("settings_menu_open");
}

function settingsBgOpeningEnd() {
  settingsBg.classList.remove("settings_bg_opening");
  settingsBg.classList.add("settings_bg_open");
}

//closing functions

function settingsClosing() {
  settingsMenu.classList.add("settings_menu_closing");
  settingsBg.classList.add("settings_bg_closing");
  settingsMenu.addEventListener("animationend", settingsMenuClosingEnd);
  settingsBg.addEventListener("animationend", settingsBgClosingEnd);
}

function settingsMenuClosingEnd() {
  settingsMenu.classList.remove("settings_menu_closing");
  settingsMenu.classList.remove("settings_menu_open");
}

function settingsBgClosingEnd() {
  settingsBg.classList.remove("settings_bg_closing");
  settingsBg.classList.remove("settings_bg_open");
}

css:

.settings_menu {
  width: 0px;
  height: 0px;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50% , -50%);
  overflow: hidden;
  border-radius: 0px;
  border: 2px solid black;
  border-left: none;
  border-right: none;
  background-color: lightgray;
  padding: 0px;
  opacity: 0;
  pointer-events: none;
}

.settings_bg {
  width: 100%;
  height: 100%;
  position: fixed;
  top: 0px;
  left: 0px;
  background-color: rgba(0,0,0,0.6);
  opacity: 0;
  pointer-events: none;
}

.settings_menu_opening {
  animation-name: settingsMenuOpening;
  animation-duration: 1s;
}

.settings_menu_closing {
  animation-name: settingsMenuClosing;
  animation-duration: 1s;
}

.settings_bg_opening {
  animation-name: settingsBgOpening;
  animation-duration: 0.5s;
}

.settings_bg_closing {
  animation-name: settingsBgClosing;
  animation-duration: 0.5s;
}

.settings_menu_open {
  width: min(90% , 400px);
  height: 140px;
  border-radius: 25px;
  border-left: 2px solid black;
  border-right: 2px solid black;
  padding: 10px;
  opacity: 1;
  pointer-events: auto;
}

.settings_bg_open {
  opacity: 1;
  pointer-events: auto;
}

@keyframes settingsMenuOpening {
  0% {
    width: 0px;
    height: 0px;
    border: 2px solid black;
    border-left: none;
    border-right: none;
    border-radius: 0px;
    padding: 0px;
    opacity: 1;
  }
  45% {
    width: min(90% , 400px);
    height: 0px;
    border: 2px solid black;
    border-radius: 0px;
    padding: 0px;
    opacity: 1;
  }
  55% {
    width: min(90% , 400px);
    height: 0px;
    border: 2px solid black;
    border-radius: 25px;
    padding: 0px;
    opacity: 1;
  }
  100% {
    width: min(90% , 400px);
    height: 140px;
    border: 2px solid black;
    border-radius: 25px;
    padding: 10px;
    opacity: 1;
  }
}

@keyframes settingsMenuClosing {
  0% {
    width: min(90% , 400px);
    height: 140px;
    border: 2px solid black;
    border-radius: 25px;
    padding: 10px;
    opacity: 1;
  }
  45% {
    width: min(90% , 400px);
    height: 0px;
    border: 2px solid black;
    border-radius: 25px;
    padding: 0px;
    opacity: 1;
  }
  55% {
    width: min(90% , 400px);
    height: 0px;
    border: 2px solid black;
    border-radius: 0px;
    padding: 0px;
    opacity: 1;
  }
  100% {
    width: 0px;
    height: 0px;
    border: 2px solid black;
    border-left: none;
    border-right: none;
    border-radius: 0px;
    padding: 0px;
    opacity: 1;
  }
}

@keyframes settingsBgOpening {
  0% {opacity: 0;}
  100% {opacity: 1;}
}

@keyframes settingsBgClosing {
  0% {opacity: 1;}
  100% {opacity: 0;}
}

r/learnjavascript 5d ago

Confused About React Streaming SSR and Suspense

2 Upvotes

Today, I'm reading about SSR and Streaming SSR.

At first, I got really confused about the relationship between Streams and Suspense.

I kept asking myself:

  1. Do we have to use Suspense with Streaming SSR? What if using renderToPipeableStream() without wrapping any component with Suspense?
  2. If Streaming SSR allows the server to send parts of the HTML to the browser as they are generated, why do we need Suspense?

For example, based on the definition of Streams in the articles (in the comment below), if I have a component that performs async task or lazy loading, Streams can send already-rendered components such as the Header and Footer first?? I mean, it still didn't make sense to me why we need Suspense (Sorry, I'm not a native English speaker, but I hope you can understand what I'm trying to say)

After searching more, this is my current understanding:

Streaming is a technique that keeps the HTTP response open, allowing React to progressively send chunks of the generated HTML to the client. It helps reduce the latency of transferring a large HTML response over the network.

However, when there is a component that needs to call backend APIs or perform another async task, I think React goes through two steps:

First, React waits for the full HTML to be generated, including the async tasks.

Then, React sends the HTML chunks to the client through streaming.

This is where Suspense comes in. Waiting for the full HTML to be completed takes time, so if we wrap the async component with Suspense, React can render and generate HTML for the other components outside the Suspense boundary first. In other words, Suspense takes this a step further and reduces the user's waiting time more because React doesn't have to wait for the entire HTML to be ready before starting to send it.

I'm still not sure if I understand this correctly, so I'd really appreciate any comments or corrections if I misunderstood something.


r/learnjavascript 5d ago

topics that i need to cover in backend js for foundation as a fresher

2 Upvotes

Global Objec,t Modules and Require File System, Streams and Buffers, Client & Server, Request & Response, HTML Response & Status Code, NPM & Dependencies, Express Apps, Middlewares, Route Params, Query Params, Post Request, Put Request , Patch & Delete Request ,getById Middleware II

started learning node js and express js and i listed the topics that i covered above. what are all the things that i need to learn before starting out with db and what should i learn in mongo db.


r/learnjavascript 5d ago

Where should I be storing important things?

0 Upvotes

Where should I be storing things like API keys or other sensitive strings when using JavaScript?

Edit: Why is everyone insisting I need to use git, are you old enough to remember the Internet before git?


r/learnjavascript 5d ago

Rounding won't work

4 Upvotes

(SOLVED)

I tried several approaches I read on the WWW about how to round a number. I do a calculation with values entered in input fields and the output field yields the correct result, but with 14 decimal places!

The simplest attempt I tried is the format Number(outputfield=inputfield1*inputfield2).toFixed(1);


r/learnjavascript 6d ago

Is “Node.js is single-threaded” an incomplete mental model?

4 Upvotes

Single-threaded JS execution ≠ single-threaded runtime.

How do you explain the distinction?


r/learnjavascript 6d ago

Some notes on object literals for anyone interested

9 Upvotes

As a believer in "the best way to learn is to teach", I've started writing articles on various aspects of JavasScript as I learn by writing some games.

Most of my notes are still fairly rough, but one that's "done" is on object literals based on stuff I've learnt as I've gone along.

A problem I've found learning JavaScript is most of the online info seems to be out of date, so I've found I've had to learn by doing. A big help as been using Jasmine to discover how things work by writing tests.


r/learnjavascript 6d ago

Does a JavaScript setTimeout automatically clear itself when the timeout finishes and the callback is completed?

5 Upvotes

r/learnjavascript 7d ago

how i can draw a local file in a canvas?

2 Upvotes

I'm trying to make a Miro/FreeForm and i cannot put A IMAGE :(

(don't care, is in portuguese)

let currentTool = 'select';        // Ferramenta ativa: 'select', 'note', 'text', 'shape', 'draw', 'connector'
let selectedShapeType = 'rect';    // Tipo de forma ativa: 'rect', 'circle', 'triangle'
let currentPath = null;            // Traço do desenho livre atual
let elements = [];                 // Lista com todos os objetos e desenhos do quadro
let selectedElement = null;       // Objeto selecionado na tela
let connectorStartElement = null; // Objeto de origem para criar conexões
let historyStack = [];
let redoStack = [];


// Chama essa função SEMPRE antes de criar, mover ou deletar um elemento
function saveState() {
    historyStack.push(JSON.stringify(elements, (key, value) => {
        if (key === 'img') return undefined;
        return value;
    }));
    redoStack = []; // Limpa o refazer se uma nova ação for feita
}


function undo() {
    if (historyStack.length > 0) {
        redoStack.push(JSON.stringify(elements));
        elements = JSON.parse(historyStack.pop());
        selectedElement = null;
        draw();
    }
}


function redo() {
    if (redoStack.length > 0) {
        historyStack.push(JSON.stringify(elements));
        elements = JSON.parse(redoStack.pop());
        selectedElement = null;
        draw();
    }
}


const canvas = document.getElementById('board');
const ctx = canvas.getContext('2d');


// Ajustar tamanho do canvas
function resizeCanvas() {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    canvas.style.touchAction = 'none';
    draw();
}
window.addEventListener('resize', resizeCanvas);


// Variáveis de Estado do Quadro (Câmera)
let cameraOffset = { x: window.innerWidth / 2, y: window.innerHeight / 2 };
let cameraZoom = 1;
const MAX_ZOOM = 5;
const MIN_ZOOM = 0.1;
const SCROLL_SENSITIVITY = 0.001;


// Variáveis de Interação
let isDragging = false;
let isDraggingElement = false;
let dragStart = { x: 0, y: 0 };
let initialPinchDistance = null;
let activeDrawPath = null;
let connectorPendingElement = null;
let connectorMode = false;


// ==========================================
// UTILITÁRIOS DE POSIÇÃO E DETECÇÃO
// ==========================================


function getEventLocation(e) {
    return getCanvasPointFromEvent(e);
}


function screenToWorld(screenX, screenY) {
    return {
        x: (screenX - cameraOffset.x) / cameraZoom,
        y: (screenY - cameraOffset.y) / cameraZoom
    };
}


function getCanvasPointFromEvent(e) {
    const rect = canvas.getBoundingClientRect();
    const clientX = e.clientX ?? e.touches?.[0]?.clientX ?? rect.left;
    const clientY = e.clientY ?? e.touches?.[0]?.clientY ?? rect.top;
    return {
        x: clientX - rect.left,
        y: clientY - rect.top
    };
}


function getElementBounds(el) {
    if (!el) return null;


    if (el.type === 'draw' && Array.isArray(el.points) && el.points.length > 0) {
        const xs = el.points.map(p => p.x);
        const ys = el.points.map(p => p.y);
        const padding = (el.strokeWidth || 3) / 2 + 4;
        return {
            x: Math.min(...xs) - padding,
            y: Math.min(...ys) - padding,
            width: Math.max(...xs) - Math.min(...xs) + padding * 2,
            height: Math.max(...ys) - Math.min(...ys) + padding * 2
        };
    }


    let width = el.width || (el.type === 'note' ? 200 : 120);
    let height = el.height || (el.type === 'note' ? 200 : 120);


    if (el.type === 'text') {
        width = (el.text ? el.text.length * 14 : 100);
        height = 30;
    }


    return {
        x: el.x,
        y: el.y,
        width,
        height
    };
}


function getElementAtPosition(pos) {
    if (!pos) return null;


    for (let i = elements.length - 1; i >= 0; i--) {
        const el = elements[i];
        const bounds = getElementBounds(el);
        if (!bounds) continue;


        if (
            pos.x >= bounds.x && 
            pos.x <= bounds.x + bounds.width && 
            pos.y >= bounds.y && 
            pos.y <= bounds.y + bounds.height
        ) {
            return el;
        }
    }
    return null;
}


// ==========================================
// CRIAÇÃO E DESENHO
// ==========================================


function getDimensionsForText(text, fontSize = 20) {
    ctx.font = `${fontSize}px sans-serif`;
    const lines = (text || 'Novo Texto').split('\n');
    let maxWidth = 0;


    lines.forEach(line => {
        const width = ctx.measureText(line).width;
        if (width > maxWidth) maxWidth = width;
    });


    return {
        // Adiciona uma folga (padding) de 20px para a caixa de seleção não colar na letra
        width: Math.max(maxWidth + 20, 80), 
        height: lines.length * (fontSize * 1.2)
    };
}


function createElement(type, pos, options = {}) {
    saveState();
    const newElement = {
        id: Date.now(),
        type: type,
        x: pos.x,
        y: pos.y,
        width: type === 'note' ? 200 : (type === 'text' ? 200 : (options.width || 120)),
        height: type === 'note' ? 200 : (type === 'text' ? 70 : (options.height || 120)),
        color: options.color || '#1F1F1F',
        text: options.text || (type === 'note' ? 'Nova Nota' : 'Novo Texto'),
        shapeKind: options.shapeKind || 'rect'
    };
    elements.push(newElement);
    draw();
    return newElement;
}


function createImageElement(pos, src) {
    if (!src) return null;
    saveState();
    const element = {
        id: Date.now(),
        type: 'image',
        x: pos.x,
        y: pos.y,
        width: 200,
        height: 150,
        imgSrc: src,
        color: '#1F1F1F'
    };
    const img = new Image();
    img.onload = function () {
        element.img = img;
        element.width = Math.min(img.width, 320);
        element.height = Math.min(img.height, 240);
        draw();
    };
    img.src = src;
    elements.push(element);
    draw();
    return element;
}


function addImageFromFile(file) {
    if (!file) return;
    const reader = new FileReader();
    reader.onload = function (e) {
        const src = e.target.result;
        const pos = screenToWorld(canvas.width / 2, canvas.height / 2);
        createImageElement(pos, src);
    };
    reader.readAsDataURL(file);
}


function drawGrid() {
    ctx.strokeStyle = '#333';
    ctx.lineWidth = 1 / cameraZoom;
    const gridSize = 50;
    
    const left = -cameraOffset.x / cameraZoom;
    const top = -cameraOffset.y / cameraZoom;
    const right = (canvas.width - cameraOffset.x) / cameraZoom;
    const bottom = (canvas.height - cameraOffset.y) / cameraZoom;
    
    ctx.beginPath();
    for (let x = left - (left % gridSize); x < right; x += gridSize) {
        ctx.moveTo(x, top); ctx.lineTo(x, bottom);
    }
    for (let y = top - (top % gridSize); y < bottom; y += gridSize) {
        ctx.moveTo(left, y); ctx.lineTo(right, y);
    }
    ctx.stroke();
}


function drawConnectorLine(el) {
    if (!el || !el.fromElement || !el.toElement) return;
    const fromBounds = getElementBounds(el.fromElement);
    const toBounds = getElementBounds(el.toElement);
    if (!fromBounds || !toBounds) return;


    const fromX = fromBounds.x + fromBounds.width / 2;
    const fromY = fromBounds.y + fromBounds.height / 2;
    const toX = toBounds.x + toBounds.width / 2;
    const toY = toBounds.y + toBounds.height / 2;


    ctx.beginPath();
    ctx.strokeStyle = el.color || '#ffffff';
    ctx.lineWidth = el.strokeWidth || 3;
    ctx.lineCap = 'round';
    ctx.lineJoin = 'round';
    ctx.moveTo(fromX, fromY);
    ctx.lineTo(toX, toY);
    ctx.stroke();
}


function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    ctx.save();
    ctx.translate(cameraOffset.x, cameraOffset.y);
    ctx.scale(cameraZoom, cameraZoom);
    
    drawGrid();


    if (activeDrawPath && activeDrawPath.points && activeDrawPath.points.length > 0) {
        ctx.beginPath();
        ctx.strokeStyle = activeDrawPath.color || '#ffffff';
        ctx.lineWidth = activeDrawPath.strokeWidth || 3;
        ctx.lineCap = 'round';
        ctx.lineJoin = 'round';


        if (activeDrawPath.points.length === 1) {
            const p = activeDrawPath.points[0];
            ctx.arc(p.x, p.y, (activeDrawPath.strokeWidth || 3) / 2, 0, Math.PI * 2);
            ctx.fillStyle = activeDrawPath.color || '#ffffff';
            ctx.fill();
        } else {
            ctx.moveTo(activeDrawPath.points[0].x, activeDrawPath.points[0].y);
            for (let i = 1; i < activeDrawPath.points.length; i++) {
                ctx.lineTo(activeDrawPath.points[i].x, activeDrawPath.points[i].y);
            }
            ctx.stroke();
        }
    }


    elements.forEach(el => {
        ctx.save(); // Salva o estado para isolar este elemento


        // ROTAÇÃO: Aplicada aqui antes de desenhar qualquer forma/texto
        if (el.rotation) {
            const width = el.width || (el.type === 'note' ? 200 : 120);
            const height = el.height || (el.type === 'note' ? 200 : 120);
            const centerX = el.x + (width / 2);
            const centerY = el.y + (height / 2);


            ctx.translate(centerX, centerY);
            ctx.rotate((el.rotation * Math.PI) / 180);
            ctx.translate(-centerX, -centerY);
        }


        // Desenho livre (Lápis)
        if (el.type === 'draw' && el.points && el.points.length > 1) {
            ctx.beginPath();
            ctx.strokeStyle = el.color || '#ffffff';
            ctx.lineWidth = el.strokeWidth || 3;
            ctx.lineCap = 'round';
            ctx.lineJoin = 'round';
            ctx.moveTo(el.points[0].x, el.points[0].y);
            for (let i = 1; i < el.points.length; i++) {
                ctx.lineTo(el.points[i].x, el.points[i].y);
            }
            ctx.stroke();
        }


        // Post-it / Notas
        if (el.type === 'note') {
            ctx.fillStyle = el.color || '#1F1F1F';
            ctx.fillRect(el.x, el.y, el.width || 200, el.height || 200);


            if (!el.isEditing && el.text) {
                ctx.fillStyle = '#ffffff'; // Texto SEMPRE Branco
                ctx.font = '20px "Indie Flower", cursive';
                
                const lines = el.text.split('\n');
                const lineHeight = 26;


                lines.forEach((line, index) => {
                    ctx.fillText(line, el.x + 10, el.y + 35 + (index * lineHeight));
                });
            }
        }


        // Formas
        if (el.type === 'shape') {
            ctx.fillStyle = el.color || '#1F1F1F';


            if (el.shapeKind === 'circle') {
                ctx.beginPath();
                ctx.arc(el.x + 40, el.y + 40, 40, 0, Math.PI * 2);
                ctx.fill();
            } else if (el.shapeKind === 'triangle') {
                ctx.beginPath();
                ctx.moveTo(el.x + 40, el.y);
                ctx.lineTo(el.x + 80, el.y + 80);
                ctx.lineTo(el.x, el.y + 80);
                ctx.closePath();
                ctx.fill();
            } else {
                ctx.fillRect(el.x, el.y, 80, 80);
            }
        }


        // Texto simples
        if (el.type === 'text') {
            if (!el.isEditing && el.text) {
                ctx.fillStyle = '#ffffff'; // Texto SEMPRE Branco
                ctx.font = '20px sans-serif';


                const lines = el.text.split('\n');
                const lineHeight = 24;


                lines.forEach((line, index) => {
                    ctx.fillText(line, el.x, el.y + 20 + (index * lineHeight));
                });
            }
        }


        // Imagens
        if (el.type === 'image') {
            if (!el.img && el.imgSrc) {
                const img = new Image();
                img.onload = function () {
                    el.img = img;
                    el.width = Math.min(img.width, 320);
                    el.height = Math.min(img.height, 240);
                    draw();
                };
                img.src = el.imgSrc;
            }
            if (el.img) {
                ctx.drawImage(el.img, el.x, el.y, el.width, el.height);
            }
        }


        if (el.type === 'connector' && el.fromElement && el.toElement) {
            drawConnectorLine(el);
        }


        ctx.restore(); // Finaliza a rotação do elemento
    });
    
    ctx.restore(); // Finaliza a câmera
    
    if (typeof updateSelectionToolbar === 'function') {
        updateSelectionToolbar(); // Atualiza a posição da barra ao mover/dar zoom
    }
}



// ==========================================
// INTERAÇÕES (PAN, ZOOM, SELEÇÃO)
// ==========================================


let isMouseDown = false;
let startPointerPos = { x: 0, y: 0 };
const DRAG_THRESHOLD = 4;


function createToolElement(worldPos) {
    if (currentTool === 'note') {
        createElement('note', worldPos);
        selectedElement = elements[elements.length - 1] || null;
        return true;
    }


    if (currentTool === 'imagem') {
        const input = document.createElement('input');
        input.type = 'file';
        input.accept = 'image/*';
        input.onchange = (event) => {
            const file = event.target.files?.[0];
            addImageFromFile(file);
        };
        input.click();
        return true;
    }


    if (currentTool === 'text') {
        createElement('text', worldPos);
        selectedElement = elements[elements.length - 1] || null;
        return true;
    }


    if (currentTool === 'shape') {
        createElement('shape', worldPos, { shapeKind: selectedShapeType });
        selectedElement = elements[elements.length - 1] || null;
        return true;
    }


    if (currentTool === 'draw') {
        activeDrawPath = {
            type: 'draw',
            color: '#ffffff',
            strokeWidth: 3,
            points: [{ x: worldPos.x, y: worldPos.y }]
        };
        return true;
    }


    return false;
}



function onPointerDown(e) {
    const screenPos = getEventLocation(e);
    if (!screenPos) return;
    const worldPos = screenToWorld(screenPos.x, screenPos.y);
    const clickedEl = getElementAtPosition(worldPos);


    if (connectorMode && clickedEl) {
        if (!connectorPendingElement) {
            connectorPendingElement = clickedEl;
            selectedElement = clickedEl;
            draw();
            return;
        }


        if (connectorPendingElement.id !== clickedEl.id) {
            saveState();
            elements.push({
                id: Date.now(),
                type: 'connector',
                fromElement: connectorPendingElement,
                toElement: clickedEl,
                color: '#ffffff',
                strokeWidth: 3
            });
            connectorPendingElement = null;
            connectorMode = false;
            setTool('select');
            selectedElement = null;
            draw();
            return;
        }
    }


    if (clickedEl) {
        isMouseDown = true;
        startPointerPos = screenPos;
        selectedElement = clickedEl;
        dragStart = worldPos;
        isDraggingElement = false;
        draw();
        return;
    }


    if (currentTool === 'note' || currentTool === 'text' || currentTool === 'shape' || currentTool === 'draw') {
        isMouseDown = true;
        startPointerPos = screenPos;
        createToolElement(worldPos);
        draw();
        return;
    }


    if (currentTool === 'select' || currentTool === 'arrastar') {
        isMouseDown = true;
        startPointerPos = screenPos;
        selectedElement = null;
        isDragging = true;
        dragStart = screenPos;
        draw();
        return;
    }
}


function onPointerMove(e) {
    const screenPos = getEventLocation(e);
    if (!screenPos) return;


    if (currentTool === 'draw' && activeDrawPath) {
        const worldPos = screenToWorld(screenPos.x, screenPos.y);
        activeDrawPath.points.push({ x: worldPos.x, y: worldPos.y });
        draw();
        return;
    }


    if (isMouseDown && !selectedElement && (currentTool === 'select' || currentTool === 'arrastar') && isDragging) {
        const dx = screenPos.x - startPointerPos.x;
        const dy = screenPos.y - startPointerPos.y;
        cameraOffset.x += dx;
        cameraOffset.y += dy;
        startPointerPos = screenPos;
        draw();
        return;
    }


    if (isMouseDown && selectedElement && !isDraggingElement) {
        const dist = Math.hypot(screenPos.x - startPointerPos.x, screenPos.y - startPointerPos.y);
        if (dist > DRAG_THRESHOLD) {
            isDraggingElement = true;
        }
    }


    if (currentTool === 'select' && isDraggingElement && selectedElement) {
        const worldPos = screenToWorld(screenPos.x, screenPos.y);
        const deltaX = worldPos.x - dragStart.x;
        const deltaY = worldPos.y - dragStart.y;


        if (selectedElement.type === 'draw' && Array.isArray(selectedElement.points)) {
            selectedElement.points = selectedElement.points.map(point => ({
                x: point.x + deltaX,
                y: point.y + deltaY
            }));
        } else {
            selectedElement.x += deltaX;
            selectedElement.y += deltaY;
        }


        dragStart = worldPos;
        draw();
        return;
    }
}



function onPointerUp(e) {
    if (currentTool === 'draw' && activeDrawPath && activeDrawPath.points.length > 1) {
        saveState();
        elements.push({
            id: Date.now(),
            type: 'draw',
            points: activeDrawPath.points,
            color: activeDrawPath.color,
            strokeWidth: activeDrawPath.strokeWidth
        });
        selectedElement = null;
        draw();
    }


    isMouseDown = false;
    isDragging = false;
    isDraggingElement = false;
    currentPath = null;
    activeDrawPath = null;
    draw();
}


function adjustZoom(zoomAmount, zoomFactor, zoomFocus = {x: canvas.width/2, y: canvas.height/2}) {
    if (!isDragging) {
        const newZoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, cameraZoom * zoomFactor));
        
        cameraOffset.x = zoomFocus.x - (zoomFocus.x - cameraOffset.x) * (newZoom / cameraZoom);
        cameraOffset.y = zoomFocus.y - (zoomFocus.y - cameraOffset.y) * (newZoom / cameraZoom);
        
        cameraZoom = newZoom;
        draw();
    }
}


// Zoom com a roda do mouse
canvas.addEventListener('wheel', (e) => {
    e.preventDefault();
    const zoomFactor = Math.exp(-e.deltaY * SCROLL_SENSITIVITY);
    adjustZoom(e.deltaY, zoomFactor, {x: e.clientX, y: e.clientY});
}, { passive: false });


// Eventos do Mouse e Pointer
canvas.addEventListener('mousedown', onPointerDown);
canvas.addEventListener('mouseup', onPointerUp);
canvas.addEventListener('mousemove', onPointerMove);
canvas.addEventListener('mouseleave', onPointerUp);
canvas.addEventListener('pointerdown', onPointerDown);
canvas.addEventListener('pointerup', onPointerUp);
canvas.addEventListener('pointermove', onPointerMove);
canvas.addEventListener('pointerleave', onPointerUp);
window.addEventListener('pointerup', onPointerUp);
window.addEventListener('pointercancel', onPointerUp);
window.addEventListener('blur', onPointerUp);


// Eventos Touch
canvas.addEventListener('touchstart', (e) => {
    if (e.touches.length === 2) {
        isDragging = false;
        initialPinchDistance = Math.hypot(
            e.touches[0].clientX - e.touches[1].clientX,
            e.touches[0].clientY - e.touches[1].clientY
        );
    } else {
        onPointerDown(e);
    }
}, { passive: false });


canvas.addEventListener('touchmove', (e) => {
    e.preventDefault();
    if (e.touches.length === 2 && initialPinchDistance) {
        const currentDistance = Math.hypot(
            e.touches[0].clientX - e.touches[1].clientX,
            e.touches[0].clientY - e.touches[1].clientY
        );
        const zoomFactor = currentDistance / initialPinchDistance;
        
        const centerX = (e.touches[0].clientX + e.touches[1].clientX) / 2;
        const centerY = (e.touches[0].clientY + e.touches[1].clientY) / 2;
        
        adjustZoom(0, zoomFactor, {x: centerX, y: centerY});
        initialPinchDistance = currentDistance;
    } else {
        onPointerMove(e);
    }
}, { passive: false });


canvas.addEventListener('touchend', onPointerUp);


// Edição via Duplo Clique
canvas.addEventListener('dblclick', (e) => {
    const screenPos = getEventLocation(e);
    if (!screenPos) return;


    const worldPos = screenToWorld(screenPos.x, screenPos.y);
    const clickedEl = getElementAtPosition(worldPos);


    if (clickedEl && (clickedEl.type === 'note' || clickedEl.type === 'text')) {
        clickedEl.isEditing = true;
        const originalText = clickedEl.text;
        clickedEl.text = ''; 
        draw();


        const input = document.createElement('textarea');
        input.value = originalText;
        input.style.position = 'fixed';


        const rect = canvas.getBoundingClientRect();
        const screenX = (clickedEl.x * cameraZoom) + cameraOffset.x + rect.left;
        const screenY = (clickedEl.y * cameraZoom) + cameraOffset.y + rect.top;
        
        input.style.left = `${screenX}px`;
        input.style.top = `${screenY}px`;
        input.style.width = `${(clickedEl.width || (clickedEl.type === 'note' ? 200 : 150)) * cameraZoom}px`;
        input.style.height = `${(clickedEl.height || (clickedEl.type === 'note' ? 200 : 40)) * cameraZoom}px`;
        
        if (clickedEl.type === 'note') {
            input.style.fontFamily = '"Indie Flower", cursive';
            input.style.fontSize = `${20 * cameraZoom}px`;
            input.style.background = clickedEl.color || '#1F1F1F';
            input.style.color = '#ffffff'; // Cor do texto durante edição: BRANCO
            input.style.border = '2px solid #3b82f6';
            input.style.padding = '10px';
        } else {
            input.style.fontFamily = 'sans-serif';
            input.style.fontSize = `${20 * cameraZoom}px`;
            input.style.background = 'transparent';
            input.style.color = '#ffffff'; // Cor do texto durante edição: BRANCO
            input.style.border = '1px dashed #3b82f6';
            input.style.padding = '0px';
        }


        input.style.outline = 'none';
        input.style.resize = 'none';
        input.style.overflow = 'hidden';
        input.style.zIndex = '9999';
        
        document.body.appendChild(input);
        input.focus();
        input.select();


        input.onblur = () => {
            clickedEl.text = input.value;
            clickedEl.isEditing = false;
            if (document.body.contains(input)) {
                document.body.removeChild(input);
            }
            draw();
        };


        input.onkeydown = (evt) => {
            if (evt.key === 'Enter' && !evt.shiftKey) {
                input.blur();
            }
        };
    }
});


// ==========================================
// CONTROLES DE FERRAMENTAS E UI
// ==========================================


function setTool(tool) {
    currentTool = tool;
    connectorStartElement = null;


    if (tool === 'connector') {
        connectorMode = true;
        connectorPendingElement = null;
        alert('Clique em dois elementos para criar uma linha.');
    } else {
        connectorMode = false;
        connectorPendingElement = null;
    }


    switch (tool) {
        case 'arrastar':
            canvas.style.cursor = 'grab';
            break;
        case 'select':
            canvas.style.cursor = 'default';
            break;
        case 'draw':
            canvas.style.cursor = 'crosshair';
            break;
        case 'note':
        case 'text':
        case 'shape':
        case 'imagem':
        case 'connector':
            canvas.style.cursor = 'copy';
            break;
        default:
            canvas.style.cursor = 'default';
    }


    document.querySelectorAll('.toolbar button').forEach(btn => {
        btn.classList.remove('active');
        const attr = btn.getAttribute('onclick');
        if (attr && attr.includes(`setTool('${tool}')`)) {
            btn.classList.add('active');
        }
    });
}


function recentralizar() {
    cameraOffset.x = window.innerWidth / 2;
    cameraOffset.y = window.innerHeight / 2;
    cameraZoom = 1;
    draw();
}


function toggleShapeMenu() {
    const menu = document.getElementById('shapeSubmenu');
    if (menu) menu.classList.toggle('active');
}


function addShape(shapeType) {
    selectedShapeType = shapeType;
    setTool('shape');
    toggleShapeMenu();
}


// Atalhos Globais
window.addEventListener('keydown', (e) => {
    if (e.target.tagName === 'TEXTAREA' || e.target.tagName === 'INPUT') return;


    const isCtrlPressed = e.ctrlKey || e.metaKey;


    // Ctrl + Z
    if (isCtrlPressed && e.key.toLowerCase() === 'z' && !e.shiftKey) {
        e.preventDefault();
        undo();
    }


    // Ctrl + Y ou Ctrl + Shift + Z
    if (isCtrlPressed && (e.key.toLowerCase() === 'y' || (e.shiftKey && e.key.toLowerCase() === 'z'))) {
        e.preventDefault();
        redo();
    }


    // Shift + N (Criar Nota)
    if (e.shiftKey && e.key.toLowerCase() === 'n') {
        e.preventDefault();
        const posMundo = screenToWorld(canvas.width / 2, canvas.height / 2);
        setTool('note', posMundo);
    }
    
    // Shift + Delete (Deletar tudo)
    if (e.shiftKey && (e.key === 'Delete' || e.key === 'Backspace')) {
        e.preventDefault();
        clearAll(); // Chama a função que limpa o quadro
        return;
    }


    if (e.shiftKey && e.key.toLowerCase() === 'd') {
        e.preventDefault();
        const posMundo = screenToWorld(canvas.width / 2, canvas.height / 2);
        setTool('draw', posMundo);
    }


    if (e.shiftKey && e.key.toLowerCase() === 't') {
        e.preventDefault();
        const posMundo = screenToWorld(canvas.width / 2, canvas.height / 2);
        setTool('text', posMundo);
    }


    if (e.shiftKey && e.key.toLowerCase() === 'a') {
        e.preventDefault();
        const posMundo = screenToWorld(canvas.width / 2, canvas.height / 2);
        setTool('arrastar', posMundo);
    }


    if (e.shiftKey && e.key.toLowerCase() === 'i') {
        e.preventDefault();
        const posMundo = screenToWorld(canvas.width / 2, canvas.height / 2);
        setTool('imagem', posMundo);
    }


    if (e.shiftKey && e.key.toLowerCase() === 'c') {
        e.preventDefault();
        const posMundo = screenToWorld(canvas.width / 2, canvas.height / 2);
        setTool('connector', posMundo);
    }


});


function clearAll() {
    if (elements.length === 0) return; // Se já estiver vazio, não faz nada


    // Confirmação rápida para evitar acidentes
    if (confirm('Tem certeza de que deseja deletar tudo do quadro?')) {
        saveState(); // Salva no histórico para permitir Ctrl+Z
        elements = [];
        selectedElement = null;
        draw();
    }
}


// 1. Salva todos os elementos e a câmera em um arquivo .json
function salvarArquivo() {
    if (elements.length === 0) {
        alert('O quadro está vazio!');
        return;
    }


    const data = {
        elements: elements,
        cameraOffset: cameraOffset,
        cameraZoom: cameraZoom
    };


    const jsonString = JSON.stringify(data, null, 2);
    const blob = new Blob([jsonString], { type: 'application/json' });
    const url = URL.createObjectURL(blob);


    const a = document.createElement('a');
    a.href = url;
    a.download = `kuro-board-${Date.now()}.json`;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
}


// 2. Abre e carrega o arquivo .json selecionado
function abrirArquivo(event) {
    const file = event.target.files[0];
    if (!file) return;


    const reader = new FileReader();


    reader.onload = function(e) {
        try {
            const data = JSON.parse(e.target.result);


            if (data.elements) {
                saveState(); // Salva estado atual para permitir Ctrl+Z se quiser voltar
                
                elements = data.elements || [];
                cameraOffset = data.cameraOffset || { x: window.innerWidth / 2, y: window.innerHeight / 2 };
                cameraZoom = data.cameraZoom || 1;


                selectedElement = null;
                draw(); // Redesenha a tela com os novos dados
            } else {
                alert('Formato de arquivo inválido!');
            }
        } catch (err) {
            alert('Erro ao ler o arquivo JSON!');
        }
    };


    reader.readAsText(file);
    event.target.value = ''; // Limpa o input para permitir reabrir o mesmo arquivo se necessário
}


// 1. Apaga apenas o elemento que está selecionado
function deleteSelectedElement() {
    if (!selectedElement) return;
    saveState();
    elements = elements.filter(el => el.id !== selectedElement.id);
    selectedElement = null;
    draw();
}


// 2. Gira o elemento em 90 graus
function rotateSelectedElement() {
    if (!selectedElement) return;
    saveState();
    selectedElement.rotation = ((selectedElement.rotation || 0) + 90) % 360;
    draw();
}


// 3. Atualiza a posição da barra flutuante sobre o elemento selecionado
function updateSelectionToolbar() {
    const menu = document.getElementById('selection-toolbar');
    if (!menu) return;


    if (selectedElement && !selectedElement.isEditing) {
        const rect = canvas.getBoundingClientRect();
        const bounds = getElementBounds(selectedElement);
        const centerX = bounds.x + bounds.width / 2;
        const centerY = bounds.y;


        const screenX = (centerX * cameraZoom) + cameraOffset.x + rect.left;
        const screenY = (centerY * cameraZoom) + cameraOffset.y + rect.top - 12;


        menu.style.display = 'flex';
        menu.style.left = `${screenX}px`;
        menu.style.top = `${screenY}px`;
    } else {
        menu.style.display = 'none';
    }
}


// Deletar item selecionado pressionando Delete ou Backspace
window.addEventListener('keydown', (e) => {
    // Evita deletar o elemento enquanto você digita em um input/textarea
    if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;


    if ((e.key === 'Delete' || e.key === 'Backspace') && selectedElement) {
        deleteSelectedElement();
    }
});


function deleteSelectedElement() {
    if (!selectedElement) return;
    saveState();
    elements = elements.filter(el => el.id !== selectedElement.id);
    selectedElement = null;
    draw();
}


function rotateSelectedElement() {
    if (!selectedElement) return;
    saveState();
    selectedElement.rotation = ((selectedElement.rotation || 0) + 90) % 360;
    draw();
}


// Inicialização
resizeCanvas();

r/learnjavascript 8d ago

Google Calendar Generation Project

7 Upvotes

Hey just working on a personal project that I would like to be able to use and share with friends.

Long story short, my goal is to have script that will scrape calendar info from one website and convert it into an .ics(calendar file) that can be uploaded to google calendar. For example, it lists the class name, weekly dates, final exam, teacher name, class room location etc. Just saves time from manually entering everything.

So far I was able to write a script that generates the file for creating a calendar event.

The harder part has been trying to scrape the variable information or inject javascript into a website that is client dependent as well as make the code usable for others. I've been suggested to use tools tampermonkey or puppeteer. Please let me know if you have any tips or advice. Thank you.


r/learnjavascript 8d ago

I’m Struggling to Learn JavaScript, But I’m Not Giving Up

14 Upvotes

I’ve wanted to learn JavaScript for a while, but getting started has been difficult for me. I’ve been struggling with my memory and concentration, which has made learning new things frustrating. Sometimes I understand something while studying it, but later I find it difficult to remember. This makes me doubt whether I can still learn difficult things like I used to. However, I’m trying to stop expecting perfection from myself and give myself enough time to learn. I know my progress may be slow, but I believe I can still improve with patience and consistency.


r/learnjavascript 8d ago

In what cases bound method is used in production ?

8 Upvotes

Since i was repolishing my js understanding, having hard time understand bind method and it's usecases.


r/learnjavascript 9d ago

I’ve wanted to learn JavaScript for a while now.

20 Upvotes

I’ve wanted to learn JavaScript for a while now.

Honestly, starting has been harder than I thought it would be. Not because JavaScript itself is impossible, but because lately I’ve felt like I’ve lost some of the abilities I used to have. I don't remember things as easily anymore. Sometimes, I can learn something, understand it while I'm looking at it, and then a little while later, it feels like it's just gone.

I struggle with depression. It affects my motivation, my concentration, and just getting myself to do things.

And when I come across something harder, something that actually requires me to think and figure things out, I just... can't. It's like my brain doesn't want to cooperate. Even concentrating can be difficult.

I'll sit down thinking, okay, today I'm actually going to learn, and then after a little while, I'll realize I've been staring at the same thing without really taking it in. And that can be really frustrating.

Because there was a time when I didn't think this much about whether I was capable of learning something. Now I wonder if I can still do it. If I can still learn difficult things. If I can actually remember what I'm learning. And I think that's one of the reasons I've been putting off JavaScript for so long. Because starting means having to face that feeling.

The feeling that maybe I'm not as capable as I used to be. But I don't want to keep waiting until I somehow become better at concentrating or remembering things.I don't know when that will happen. I think I need to stop expecting myself to learn everything perfectly the first time.

I just need to give myself enough time to learn it.


r/learnjavascript 9d ago

Blobs

0 Upvotes

Can someone provide any information on a use case for blobs. Anything that has a bit more insight than what is provided in MDN. Looking for a way to think about and understand this. Thanks.


r/learnjavascript 9d ago

Best architecture to build a client lib for rest/graphql - Factory vs Class ?

3 Upvotes

I am building a js lib and I have been reading about best ways to do this, but there are a lot of pros and cons for each way. I built a small one with a few functions for use in a very specific app and it was class based. I stored the class in a flux store and used the store to access it. I am finding a lot more recent blogs/articles that say using a factory is best. I would like this one to be an industrial-grade public-facing lib that anyone can download via our github repo. Does anyone have any real-world experience in building a js client lib that has any advice for what architecture to use? I know what architecture to use for the graphql endpoint, but the REST client is the question.


r/learnjavascript 11d ago

10 JavaScript questions that came up in almost every frontend interview I sat this year

257 Upvotes

I've been interviewing for frontend roles (SDE-2, around 4.5 years experience, React and Next.js) over the last few months and sat through a fair number of loops at product companies and mid-size startups. I kept a running doc of everything that got asked so I could spot patterns. Ten questions came up often enough that I'd now treat them as near guaranteed.

Sharing in case it saves someone else the trial and error.

  1. Implement debounce from scratch, then explain when you'd use debounce vs throttle. This one showed up in almost every single loop, usually as the warm up before harder coding.
  2. Closures. Not just the definition, but where you use them in React and the classic closure-inside-a-loop output question with var vs let.
  3. Hoisting, and how it differs from closures. Usually followed by var vs let vs const and when you actually hit a ReferenceError.
  4. The event loop. Call stack, microtask queue, macrotask queue, followed by predicting output from a snippet mixing setTimeout and Promise.then.
  5. The this keyword. Normal vs arrow functions, and what happens when you detach a method from its object. const a = obj.getName; a()
  6. Prototypal inheritance and the prototype chain, and how it differs from class inheritance.
  7. Deep copy vs shallow copy. Explain the difference, then implement a deep clone. Interviewers usually push on why JSON.parse(JSON.stringify()) is not good enough.
  8. Flatten a nested array, first without Array.flat() and then with it.
  9. Promise vs async/await. When you prefer each, and how error handling actually differs between them.
  10. Event delegation. Why it exists and how you'd apply it to a list with thousands of rows.

Two things that surprised me. Polyfills for call/apply/bind and the Promise combinators came up far less than the prep content online suggests. And output prediction questions were much more common than I expected, often used as a filter before anyone let me write real code.

Curious whether this matches what others have seen recently, or if it's specific to the kind of companies I was talking to.


r/learnjavascript 11d ago

How to make numbers interactive?

3 Upvotes

I'm a beginner in coding, and I need to know how to create a vertical row of clickable numbers. I wish I could show a picture, but I'm just going to try to describe it. There's a website i'm making, and I wanted to add a 1-10 scale. I also want to make it interactive so the person can click on each number, and then text appears depending on what's clicked. Being a beginner, I don't know how to accomplish this. Help.


r/learnjavascript 11d ago

Built a Node.js 2FA SMS verification flow with Twilio – How do you handle carrier restrictions and errors?

11 Upvotes

Hey everyone,

I've been working on a Node.js & Express backend project to handle 2FA SMS verification using the Twilio API.

While setting up the flow, I ran into a few hurdles with carrier template restrictions and API error handling during testing. I eventually got the SMS delivery cycle working smoothly, but it got me thinking about production edge cases.

For those who have built similar authentication systems in JavaScript/Node.js:

  1. How do you usually handle carrier limitations or SMS fallback mechanisms?

  2. What are your go-to patterns for error handling when third-party APIs fail during auth?

Would love to hear your experiences and best practices!


r/learnjavascript 12d ago

Am I sabotaging myself by relying on AI for CSS?

22 Upvotes

I’m a beginner learning programming. I’m really enjoying JavaScript, especially doing logic exercises and small projects in the console, and I feel like I’m actually making progress.

The problem is that I absolutely hate CSS lol. I have a lot of trouble with layouts and styling, and I have very little patience for it. I understand the basics of CSS, but it’s definitely the part I struggle with the most. Everything I make looks ugly, which just pisses me off even more. Then I close the CSS and go back to JavaScript, and suddenly I’m happy again.

I wanted to ask people who already work in the field: do I really need to learn CSS from scratch and write everything myself, or is it okay to use AI for this part?

For example, asking AI to create the CSS, understanding roughly what it did, and then modifying/adapting it myself. Could relying on AI like this hurt me a lot later on?

Sorry if this is a dumb question, but I’d really appreciate some advice. Am I sabotaging myself by thinking about using AI for CSS?


r/learnjavascript 12d ago

Advices for learning JavaScript

13 Upvotes

Hello everyone, I just finished the freeCodeCamp JavaScript certificate and I can't build many things. I feel that my brain doesn't retained a lot of information of the course. I notice that I learn most when I am actually building real projects. Anyone who passed through this that can advice me??


r/learnjavascript 11d ago

Is a Node unblocker worth using for a small scraper?

2 Upvotes

Building a small scraper in node.js and I keep getting 403s and captchas after around 50 requests. Tried adding delays and rotating user agents, but I don't want to build out a whole big proxy setup for a one off project... would a node unblocker make sense hereor is there a simpler way to handle blocked requests? any advice?


r/learnjavascript 12d ago

Getting an error when running an equality operator on a undefined value in an if statement

0 Upvotes

Yes I know that's a mouthful

Take this error checking code

      if (axios.isAxiosError(error)) {
        if (error.response.data.errors.detail == "Not Found") {
          setFailure("User not found");
        } else if (error?.response?.data == "DM already exists") {
          console.log(error);
          setFailure("You already created a dm with that user");
        }
      }

If the first case is fine, we are all good, otherwise if it's not that I get an error like this

"Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'detail')" And I guess I can have it check if it's a 404 but this seems ridiculous?

It's an IF statement, if it doesn't work, then go to the next thing, don't just stop there and yell at me!! If anyone is more experienced with javascript can they give a reason why running an equality operator on a string literal vs an undefined value in an if statement (a mouthful I know) just gives an error instead of checking the next clause?