Как сделать функцию выбора более эффективной?

    const canvas = document.getElementById('myCanvas');
        const ctx = canvas.getContext('2d');
        const overlay = document.getElementById('overlay');
        const fileInput = document.getElementById('fileInput');
        const titleInput = document.getElementById('titleInput');
        const urlInput = document.getElementById('urlInput');
        const submitBtn = document.getElementById('submitBtn');
        const cancelBtn = document.getElementById('cancelBtn');
        const gridSize = 1000;
        const cellSize = 20;
        const gridColor = 'rgba(0,0,0,0.2)';
        const selectionColor = 'rgba(0, 0, 255, 0.5)';
        let mouseDown = false;
        let startCell = null;
        let endCell = null;
        const occupiedCells = new Set();
        let tempSelection = null;

        function drawGrid() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.strokeStyle = gridColor;
            for (let i = 0; i <= gridSize; i++) {
                for (let j = 0; j <= gridSize; j++) {
                    ctx.strokeRect(i * cellSize, j * cellSize, cellSize, cellSize);
                }
            }
        }

        function getCellCoordinates(x, y) {
            return {
                x: Math.floor(x / cellSize),
                y: Math.floor(y / cellSize)
            };
        }

        function isCellOccupied(cell) {
            return occupiedCells.has(`${cell.x},${cell.y}`);
        }

        function isSelectionOverlapping(xMin, xMax, yMin, yMax) {
            for (let x = xMin; x <= xMax; x++) {
                for (let y = yMin; y <= yMax; y++) {
                    const currentCell = { x, y };
                    if (isCellOccupied(currentCell)) {
                        return true;
                    }
                }
            }
            return false;
        }

        function drawSelection() {
            if (!startCell || !endCell) return;

            drawGrid();
            occupiedCells.forEach(cell => {
                const [x, y] = cell.split(',');
                ctx.fillStyle = selectionColor;
                ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize);
            });

            let xMin = Math.min(startCell.x, endCell.x);
            let xMax = Math.max(startCell.x, endCell.x);
            let yMin = Math.min(startCell.y, endCell.y);
            let yMax = Math.max(startCell.y, endCell.y);

            while (isSelectionOverlapping(xMin, xMax, yMin, yMax)) {
                if (endCell.x > startCell.x) {
                    xMax--;
                } else if (endCell.x < startCell.x) {
                    xMin++;
                }
                if (endCell.y > startCell.y) {
                    yMax--;
                } else if (endCell.y < startCell.y) {
                    yMin++;
                }
            }

            tempSelection = { xMin, xMax, yMin, yMax };

            ctx.fillStyle = selectionColor;
            for (let x = xMin; x <= xMax; x++) {
                for (let y = yMin; y <= yMax; y++) {
                    ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize);
                }
            }

            ctx.fillStyle = '#000';
            ctx.fillText(`${(xMax - xMin + 1) * cellSize}x${(yMax - yMin + 1) * cellSize}`, endCell.x * cellSize, endCell.y * cellSize);
        }

        function confirmSelection() {
            const { xMin, xMax, yMin, yMax } = tempSelection;

            for (let x = xMin; x <= xMax; x++) {
                for (let y = yMin; y <= yMax; y++) {
                    const currentCell = { x, y };
                    occupiedCells.add(`${currentCell.x},${currentCell.y}`);
                }
            }

            drawGrid();
            occupiedCells.forEach(cell => {
                const [x, y] = cell.split(',');
                ctx.fillStyle = selectionColor;
                ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize);
            });
        }

        canvas.addEventListener('mousedown', (e) => {
            mouseDown = true;
            startCell = getCellCoordinates(e.offsetX, e.offsetY);
        });

        canvas.addEventListener('mousemove', (e) => {
            if (!mouseDown) return;
            endCell = getCellCoordinates(e.offsetX, e.offsetY);
            drawSelection();
        });

        canvas.addEventListener('mouseup', (e) => {
            if (!mouseDown) return;
            mouseDown = false;
            endCell = getCellCoordinates(e.offsetX, e.offsetY);
            if (!isSelectionOverlapping(tempSelection.xMin, tempSelection.xMax, tempSelection.yMin, tempSelection.yMax)) {
                confirmSelection();
            }
            startCell = null;
            endCell = null;
            overlay.style.display = 'block';
        });

        canvas.addEventListener('mouseleave', (e) => {
            if (!mouseDown) return;
            mouseDown = false;
            startCell = null;
            endCell = null;
        });

        submitBtn.addEventListener('click', () => {
            // Handle form submission here
            overlay.style.display = 'none';
            fileInput.value = '';
            titleInput.value = '';
            urlInput.value = '';
        });

        cancelBtn.addEventListener('click', () => {
            overlay.style.display = 'none';
            fileInput.value = '';
            titleInput.value = '';
            urlInput.value = '';
        });

        drawGrid();
        canvas {
            border: 1px solid rgba(0,0,0,.2);
        }

        #overlay {
            display: none;
            position: fixed;
            left: 0;
            top: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.5);
            z-index: 2;
        }

        #form-container {
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100%;
        }

        #upload-form {
            background-color: white;
            padding: 20px;
            border-radius: 5px;
        }
<!DOCTYPE html>
<html lang = "en">

<head>
    <meta charset = "UTF-8">
    <meta name = "viewport" content = "width=device-width, initial-scale=1.0">
    <title>1000x1000 Grid with Non-overlapping Selectable Boxes and File Upload</title>
    <style>
    </style>
</head>

<body>
    <canvas id = "myCanvas" width = "1000" height = "1000"></canvas>
    <div id = "overlay">
        <div id = "form-container">
            <form id = "upload-form">
                <div>
                    <label for = "fileInput">Upload File:</label>
                    <input type = "file" id = "fileInput">
                </div>
                <div>
                    <label for = "titleInput">Title:</label>
                    <input type = "text" id = "titleInput">
                </div>
                <div>
                    <label for = "urlInput">URL:</label>
                    <input type = "text" id = "urlInput">
                </div>
                <button type = "button" id = "submitBtn">Submit</button>
                <button type = "button" id = "cancelBtn">Cancel</button>
            </form>
        </div>
    </div>

</body>

</html>

Этот алгоритм выбора имеет огромное отставание. Мне сказали, что бинарный поиск для поиска занятых ячеек будет быстрее, но я не знаю, как это сделать.

Он просматривает каждую ячейку сетки, что неэффективно.

<!DOCTYPE html>
<html lang = "en">

<head>
    <meta charset = "UTF-8">
    <meta name = "viewport" content = "width=device-width, initial-scale=1.0">
    <title>1000x1000 Grid with Non-overlapping Selectable Boxes and File Upload</title>
    <style>
        canvas {
            border: 1px solid rgba(0,0,0,.2);
        }

        #overlay {
            display: none;
            position: fixed;
            left: 0;
            top: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.5);
            z-index: 2;
        }

        #form-container {
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100%;
        }

        #upload-form {
            background-color: white;
            padding: 20px;
            border-radius: 5px;
        }
    </style>
</head>

<body>
    <canvas id = "myCanvas" width = "1000" height = "1000"></canvas>
    <div id = "overlay">
        <div id = "form-container">
            <form id = "upload-form">
                <div>
                    <label for = "fileInput">Upload File:</label>
                    <input type = "file" id = "fileInput">
                </div>
                <div>
                    <label for = "titleInput">Title:</label>
                    <input type = "text" id = "titleInput">
                </div>
                <div>
                    <label for = "urlInput">URL:</label>
                    <input type = "text" id = "urlInput">
                </div>
                <button type = "button" id = "submitBtn">Submit</button>
                <button type = "button" id = "cancelBtn">Cancel</button>
            </form>
        </div>
    </div>
    <script>
        const canvas = document.getElementById('myCanvas');
        const ctx = canvas.getContext('2d');
        const overlay = document.getElementById('overlay');
        const fileInput = document.getElementById('fileInput');
        const titleInput = document.getElementById('titleInput');
        const urlInput = document.getElementById('urlInput');
        const submitBtn = document.getElementById('submitBtn');
        const cancelBtn = document.getElementById('cancelBtn');
        const gridSize = 1000;
        const cellSize = 20;
        const gridColor = 'rgba(0,0,0,0.2)';
        const selectionColor = 'rgba(0, 0, 255, 0.5)';
        let mouseDown = false;
        let startCell = null;
        let endCell = null;
        const occupiedCells = new Set();
        let tempSelection = null;

        function drawGrid() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.strokeStyle = gridColor;
            for (let i = 0; i <= gridSize; i++) {
                for (let j = 0; j <= gridSize; j++) {
                    ctx.strokeRect(i * cellSize, j * cellSize, cellSize, cellSize);
                }
            }
        }

        function getCellCoordinates(x, y) {
            return {
                x: Math.floor(x / cellSize),
                y: Math.floor(y / cellSize)
            };
        }

        function isCellOccupied(cell) {
            return occupiedCells.has(`${cell.x},${cell.y}`);
        }

        function isSelectionOverlapping(xMin, xMax, yMin, yMax) {
            for (let x = xMin; x <= xMax; x++) {
                for (let y = yMin; y <= yMax; y++) {
                    const currentCell = { x, y };
                    if (isCellOccupied(currentCell)) {
                        return true;
                    }
                }
            }
            return false;
        }

        function drawSelection() {
            if (!startCell || !endCell) return;

            drawGrid();
            occupiedCells.forEach(cell => {
                const [x, y] = cell.split(',');
                ctx.fillStyle = selectionColor;
                ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize);
            });

            let xMin = Math.min(startCell.x, endCell.x);
            let xMax = Math.max(startCell.x, endCell.x);
            let yMin = Math.min(startCell.y, endCell.y);
            let yMax = Math.max(startCell.y, endCell.y);

            while (isSelectionOverlapping(xMin, xMax, yMin, yMax)) {
                if (endCell.x > startCell.x) {
                    xMax--;
                } else if (endCell.x < startCell.x) {
                    xMin++;
                }
                if (endCell.y > startCell.y) {
                    yMax--;
                } else if (endCell.y < startCell.y) {
                    yMin++;
                }
            }

            tempSelection = { xMin, xMax, yMin, yMax };

            ctx.fillStyle = selectionColor;
            for (let x = xMin; x <= xMax; x++) {
                for (let y = yMin; y <= yMax; y++) {
                    ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize);
                }
            }

            ctx.fillStyle = '#000';
            ctx.fillText(`${(xMax - xMin + 1) * cellSize}x${(yMax - yMin + 1) * cellSize}`, endCell.x * cellSize, endCell.y * cellSize);
        }

        function confirmSelection() {
            const { xMin, xMax, yMin, yMax } = tempSelection;

            for (let x = xMin; x <= xMax; x++) {
                for (let y = yMin; y <= yMax; y++) {
                    const currentCell = { x, y };
                    occupiedCells.add(`${currentCell.x},${currentCell.y}`);
                }
            }

            drawGrid();
            occupiedCells.forEach(cell => {
                const [x, y] = cell.split(',');
                ctx.fillStyle = selectionColor;
                ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize);
            });
        }

        canvas.addEventListener('mousedown', (e) => {
            mouseDown = true;
            startCell = getCellCoordinates(e.offsetX, e.offsetY);
        });

        canvas.addEventListener('mousemove', (e) => {
            if (!mouseDown) return;
            endCell = getCellCoordinates(e.offsetX, e.offsetY);
            drawSelection();
        });

        canvas.addEventListener('mouseup', (e) => {
            if (!mouseDown) return;
            mouseDown = false;
            endCell = getCellCoordinates(e.offsetX, e.offsetY);
            if (!isSelectionOverlapping(tempSelection.xMin, tempSelection.xMax, tempSelection.yMin, tempSelection.yMax)) {
                confirmSelection();
            }
            startCell = null;
            endCell = null;
            overlay.style.display = 'block';
        });

        canvas.addEventListener('mouseleave', (e) => {
            if (!mouseDown) return;
            mouseDown = false;
            startCell = null;
            endCell = null;
        });

        submitBtn.addEventListener('click', () => {
            // Handle form submission here
            overlay.style.display = 'none';
            fileInput.value = '';
            titleInput.value = '';
            urlInput.value = '';
        });

        cancelBtn.addEventListener('click', () => {
            overlay.style.display = 'none';
            fileInput.value = '';
            titleInput.value = '';
            urlInput.value = '';
        });

        drawGrid();
    </script>
</body>

</html>

Вы не спрашивали, но я заметил, что когда вы щелкаете и перетаскиваете, начиная с выделенного прямоугольника, он сильно падает.

padeso 20.04.2023 12:12

Какова актуальность формы загрузки? Это связано с вашим вопросом?

trincot 20.04.2023 12:13

я исправил сбой.

chovy 21.04.2023 15:41
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
3
52
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы можете сильно оживить это, перерисовывая меньше. Рисовать сетку в начале функции drawSelection не нужно, потому что функция перерисовывает все измененные ячейки в конце.

function drawSelection() {
    if (!startCell || !endCell) return;

    // drawGrid();
    // occupiedCells.forEach(cell => {
    //     const [x, y] = cell.split(',');
    //     ctx.fillStyle = selectionColor;
    //     ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize);
// });
...

Кроме того, поскольку цвет выделения частично прозрачен, выделенные квадраты во время выделения становятся непрозрачными. Если это не сделано намеренно, вы должны использовать полностью непрозрачный цвет для выделения. Тогда вам не нужно было бы перерисовывать сетку и в функции confirmSelection, чтобы всплывающее окно не отставало так сильно.

Ответ принят как подходящий

Основная задержка вызвана ошибкой, которую вы допустили в drawGrid. Переменная цикла изменяется от 0 до 999, но у вас не так много линий для рисования. Вместо того, чтобы умножать это значение на 20, вы должны сделать шаг на 20:

Поэтому измените этот цикл на:

    for (let i = 0; i <= gridSize; i+=cellSize) { // step
        for (let j = 0; j <= gridSize; j+=cellSize) {
            ctx.strokeRect(i, j, cellSize, cellSize); // no multiplication
        }
    }

Есть еще одна проблема: когда пользователь начинает перетаскивание из прямоугольника, вы попадаете в бесконечный цикл. Этого легко избежать: просто игнорируйте событие mousedown, когда оно происходит на прямоугольнике:

    canvas.addEventListener('mousedown', (e) => {
        startCell = getCellCoordinates(e.offsetX, e.offsetY);
        mouseDown = !isCellOccupied(startCell); // Must ensure not in rectangle
    });

Вот фрагмент с этими изменениями (я удалил оверлей/форму):

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

const gridSize = 1000;
const cellSize = 20;
const gridColor = 'rgba(0,0,0,0.2)';
const selectionColor = 'rgba(0, 0, 255, 0.5)';
let mouseDown = false;
let startCell = null;
let endCell = null;
const occupiedCells = new Set();
let tempSelection = null;

function drawGrid() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.strokeStyle = gridColor;
    for (let i = 0; i <= gridSize; i+=cellSize) { // step
        for (let j = 0; j <= gridSize; j+=cellSize) {
            //ctx.strokeRect(i * cellSize, j * cellSize, cellSize, cellSize);
            ctx.strokeRect(i, j, cellSize, cellSize);
        }
    }
}

function getCellCoordinates(x, y) {
    return {
        x: Math.floor(x / cellSize),
        y: Math.floor(y / cellSize)
    };
}

function isCellOccupied(cell) {
    return occupiedCells.has(`${cell.x},${cell.y}`);
}

function isSelectionOverlapping(xMin, xMax, yMin, yMax) {
    for (let x = xMin; x <= xMax; x++) {
        for (let y = yMin; y <= yMax; y++) {
            const currentCell = { x, y };
            if (isCellOccupied(currentCell)) {
                return true;
            }
        }
    }
    return false;
}

function drawSelection() {
    if (!startCell || !endCell) return;

    drawGrid();
    occupiedCells.forEach(cell => {
        const [x, y] = cell.split(',');
        ctx.fillStyle = selectionColor;
        ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize);
    });

    let xMin = Math.min(startCell.x, endCell.x);
    let xMax = Math.max(startCell.x, endCell.x);
    let yMin = Math.min(startCell.y, endCell.y);
    let yMax = Math.max(startCell.y, endCell.y);

    while (isSelectionOverlapping(xMin, xMax, yMin, yMax)) {
        if (endCell.x > startCell.x) {
            xMax--;
        } else if (endCell.x < startCell.x) {
            xMin++;
        }
        if (endCell.y > startCell.y) {
            yMax--;
        } else if (endCell.y < startCell.y) {
            yMin++;
        }
    }

    tempSelection = { xMin, xMax, yMin, yMax };

    ctx.fillStyle = selectionColor;
    for (let x = xMin; x <= xMax; x++) {
        for (let y = yMin; y <= yMax; y++) {
            ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize);
        }
    }

    ctx.fillStyle = '#000';
    ctx.fillText(`${(xMax - xMin + 1) * cellSize}x${(yMax - yMin + 1) * cellSize}`, endCell.x * cellSize, endCell.y * cellSize);
}

function confirmSelection() {
    const { xMin, xMax, yMin, yMax } = tempSelection;

    for (let x = xMin; x <= xMax; x++) {
        for (let y = yMin; y <= yMax; y++) {
            const currentCell = { x, y };
            occupiedCells.add(`${currentCell.x},${currentCell.y}`);
        }
    }

    drawGrid();
    occupiedCells.forEach(cell => {
        const [x, y] = cell.split(',');
        ctx.fillStyle = selectionColor;
        ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize);
    });
}

canvas.addEventListener('mousedown', (e) => {
    startCell = getCellCoordinates(e.offsetX, e.offsetY);
    mouseDown = !isCellOccupied(startCell); // Must ensure not in rectangle
});

canvas.addEventListener('mousemove', (e) => {
    if (!mouseDown) return;
    endCell = getCellCoordinates(e.offsetX, e.offsetY);
    drawSelection();
});

canvas.addEventListener('mouseup', (e) => {
    if (!mouseDown) return;
    mouseDown = false;
    endCell = getCellCoordinates(e.offsetX, e.offsetY);
    if (!isSelectionOverlapping(tempSelection.xMin, tempSelection.xMax, tempSelection.yMin, tempSelection.yMax)) {
        confirmSelection();
    }
    startCell = null;
    endCell = null;
});

canvas.addEventListener('mouseleave', (e) => {
    if (!mouseDown) return;
    mouseDown = false;
    startCell = null;
    endCell = null;
});

drawGrid();
<canvas id = "myCanvas" width = "1000" height = "1000"></canvas>

NB: вы можете улучшить алгоритм, чтобы прямоугольник «подходил», когда мышь перемещается по другому прямоугольнику. Чем дальше мышь перемещается по другому прямоугольнику, тем меньше становится новый прямоугольник. Я нахожу это немного нелогичным.

Спасибо. это немного тормозит, если я использую сетку 10kx10k, но это не так плохо, как было.

chovy 21.04.2023 03:00

Пожалуйста. Если с 10kx10k у вас возникли проблемы с производительностью, не стесняйтесь задавать новый вопрос об этом сценарии.

trincot 21.04.2023 08:06

Другие вопросы по теме