Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   Помогите понять Canvas (https://javascript.ru/forum/misc/74130-pomogite-ponyat-canvas.html)

MC-XOBAHCK 15.06.2018 14:02

Помогите понять Canvas
 
Не могу понять как работать с канвасом.
Вот мой пример (canvas и блок с размерами):
<style>.skat-size{display:inline-block;margin:15px 10px;padding:0 10px 10px;border:solid 1px #ccc;}</style>

<canvas id="valma" width="420" height="220">Конверт</canvas>

<div class="row plos" data-area="valma">
    <div class="skat-size">
        <h4>Трапеция</h4>
        <div>Карниз:</div>
        <div>
            <input class="valma" type="text" /><span class="unit"> м</span>
        </div>
        <div>Конёк:</div>
        <div>
            <input class="valma" type="text" /><span class="unit"> м</span>
        </div>
        <div>Высота:</div>
        <div>
            <input class="valma" type="text" /><span class="unit"> м</span>
        </div>
    </div>
    <div class="skat-size">
        <h4>Треугольный скат</h4>
        <div>Длина карниза:</div>
        <div>
            <input class="valma" type="text" /><span class="unit"> м</span>
        </div>
        <div>Высота ската:</div>
        <div>
            <input class="valma" type="text" /><span class="unit"> м</span>
        </div>
    </div>
</div>


<script>
var canvasValma = document.querySelector('canvas#valma');

function canvasRoof() {
    let valm_sk1 = '#bbb',
        valm_sk2 = '#ccc',
        valm_sk3 = '#ddd',
        valm_sk4 = '#ccc';
    
    let valma = canvasValma.getContext('2d');

    if (canvasValma && canvasValma.getContext) {
        valma.fillStyle = '#fff';
        valma.fillRect(0, 0, 420, 220);
        
        valma.beginPath();   // 1 сторона
        valma.moveTo(0, 0);
        valma.lineTo(400, 0);
        valma.lineTo(300, 100);
        valma.lineTo(100, 100);
        valma.fillStyle = valm_sk1;
        valma.fill();
        
        valma.beginPath();   // 2 сторона
        valma.moveTo(400, 0);
        valma.lineTo(300, 100);
        valma.lineTo(400, 200);
        valma.fillStyle = valm_sk2;
        valma.fill();

        valma.beginPath();   // 3 сторона
        valma.moveTo(0, 200);
        valma.lineTo(100, 100);
        valma.lineTo(300, 100);
        valma.lineTo(400, 200);
        valma.fillStyle = valm_sk3;
        valma.fill();

        valma.beginPath();   // 4 сторона
        valma.moveTo(0, 0);
        valma.lineTo(100, 100);
        valma.lineTo(0, 200);
        valma.fillStyle = valm_sk4;
        valma.fill();
    }
}


canvasRoof();

function razmStrel(color, v, lw) {   // Принимает: color - цвет, v - координаты (массив), lw - толщина линии
    let strelka = canvasValma.getContext('2d');

    if (canvasValma && canvasValma.getContext) {
        // Размерные стрелки
        strelka.strokeStyle = color;
        strelka.lineWidth = lw;
        strelka.beginPath();
        strelka.moveTo(v[0], v[1]);
        strelka.lineTo(v[2], v[3]);
        strelka.stroke();

        strelka.beginPath();
        strelka.lineWidth = 1;
        strelka.moveTo(v[4], v[5]);
        strelka.lineTo(v[6], v[7]);
        strelka.lineTo(v[8], v[9]);
        strelka.stroke();

        strelka.beginPath();
        strelka.lineWidth = 1;
        strelka.moveTo(v[10], v[11]);
        strelka.lineTo(v[12], v[13]);
        strelka.lineTo(v[14], v[15]);
        strelka.stroke();
    }
}

var inp = document.querySelectorAll('input');
for (let i = 0; i < inp.length; i++) {
    inp[i].addEventListener('focus', function() {
        let color = 'green',
            v,
            lw = 2;
        switch (i) {    //Для вальмы
            case 0: v = [0,210, 400,210, 15,207, 0,210, 15,213, 385,207, 400,210, 385,213]; break;
            case 1: v = [100,115, 300,115, 115,112, 100,115, 115,118, 285,112, 300,115, 285,118]; break;
            case 2: v = [120,100, 120,200, 117,115, 120,100, 123,115, 117,185, 120,200, 123,185]; break;
            case 3: v = [410,0, 410,200, 407,15, 410,0, 413,15, 407,185, 410,200, 413,185]; break;
            case 4: v = [300,100, 400,100, 315,97, 300,100, 315,103, 385,97, 400,100, 385,103]; break;
        }

        razmStrel(color, v, lw);
    })

    inp[i].addEventListener('blur', () => canvasRoof());
    
}
</script>

Я нарисовал фигуру, но не знаю как мне реализовать интерактивность. Нужно сигменты из канваса на отдельные части разделять или как это делается?
Например, чтобы при наведении курсора на блок с размерами (инпутами), на канвасе менял цвет соответствующий фрагмент.
А при установке фокуса в инпуте, появлялась размерная стрелка в нужном месте, при выходе из фокуса стрелка убиралась.

MC-XOBAHCK 15.06.2018 18:00

Думал-думал, попробовал несколько идей и смог функцию надумать добавляющую стрелки (код в топике исправил).

Только при фокусе в инпуте стрелки добавляются, а как их убрать когда фокус снимается???

Dilettante_Pro 15.06.2018 18:16

Цитата:

Сообщение от MC-XOBAHCK
как их убрать когда фокус снимается

Перерисовывать canvas без них

рони 15.06.2018 18:57

MC-XOBAHCK,
<!DOCTYPE html>

<html>
<head>
  <title>Untitled</title>
  <meta charset="utf-8">

</head>

<body>
<div id="slider"></div>
<style>.skat-size{display:inline-block;margin:15px 10px;padding:0 10px 10px;border:solid 1px #ccc;}</style>

<canvas id="valma-test2" width="420" height="220">Конверт</canvas>

<div class="row plos" data-area="valma">
    <div class="skat-size">
        <h4>Трапеция</h4>
        <div>Карниз:</div>
        <div>
            <input class="valma" type="text" /><span class="unit"> м</span>
        </div>
        <div>Конёк:</div>
        <div>
            <input class="valma" type="text" /><span class="unit"> м</span>
        </div>
        <div>Высота:</div>
        <div>
            <input class="valma" type="text" /><span class="unit"> м</span>
        </div>
    </div>
    <div class="skat-size">
        <h4>Треугольный скат</h4>
        <div>Длина карниза:</div>
        <div>
            <input class="valma" type="text" /><span class="unit"> м</span>
        </div>
        <div>Высота ската:</div>
        <div>
            <input class="valma" type="text" /><span class="unit"> м</span>
        </div>
    </div>
</div>


<script>
let valm_sk1 = '#bbb',
    valm_sk2 = '#ccc',
    valm_sk3 = '#ddd',
    valm_sk4 = '#ccc';
var canvasValma2 = document.querySelector('#valma-test2');
function draw()
{

    let valma = canvasValma2.getContext('2d');
    if (valma) {
        valma.clearRect(0, 0, 420, 220);
        valma.beginPath();   // 1 сторона
        valma.moveTo(0, 0);
        valma.lineTo(400, 0);
        valma.lineTo(300, 100);
        valma.lineTo(100, 100);
        valma.fillStyle = valm_sk1;
        valma.fill();

        valma.beginPath();   // 2 сторона
        valma.moveTo(400, 0);
        valma.lineTo(300, 100);
        valma.lineTo(400, 200);
        valma.fillStyle = valm_sk2;
        valma.fill();

        valma.beginPath();   // 3 сторона
        valma.moveTo(0, 200);
        valma.lineTo(100, 100);
        valma.lineTo(300, 100);
        valma.lineTo(400, 200);
        valma.fillStyle = valm_sk3;
        valma.fill();

        valma.beginPath();   // 4 сторона
        valma.moveTo(0, 0);
        valma.lineTo(100, 100);
        valma.lineTo(0, 200);
        valma.fillStyle = valm_sk4;
        valma.fill();
    }
}


    draw() ;
    function razmStrel(color, v, lw){   // Принимает: color - цвет, v - координаты (массив), lw - толщина линии
         let strelka = canvasValma2.getContext('2d');

        if (strelka) {
            // Размерные стрелки
            strelka.strokeStyle = color;
            strelka.lineWidth = lw;
            strelka.beginPath();
            strelka.moveTo(v[0], v[1]);
            strelka.lineTo(v[2], v[3]);
            strelka.stroke();

            strelka.beginPath();
            strelka.lineWidth = 1;
            strelka.moveTo(v[4], v[5]);
            strelka.lineTo(v[6], v[7]);
            strelka.lineTo(v[8], v[9]);
            strelka.stroke();

            strelka.beginPath();
            strelka.lineWidth = 1;
            strelka.moveTo(v[10], v[11]);
            strelka.lineTo(v[12], v[13]);
            strelka.lineTo(v[14], v[15]);
            strelka.stroke();
        }
    }


var inp = document.querySelectorAll('input');

[].forEach.call(inp, function(node,i) {
    let color = 'green',
            v,
            lw = 2;
        switch (i) {
            case 0: v = [0,210, 400,210, 15,207, 0,210, 15,213, 385,207, 400,210, 385,213]; break;
            case 1: v = [100,115, 300,115, 115,112, 100,115, 115,118, 285,112, 300,115, 285,118]; break;
            case 2: v = [120,100, 120,200, 117,115, 120,100, 123,115, 117,185, 120,200, 123,185]; break;
            case 3: v = [410,0, 410,200, 407,15, 410,0, 413,15, 407,185, 410,200, 413,185]; break;
            case 4: v = [300,100, 400,100, 315,97, 300,100, 315,103, 385,97, 400,100, 385,103]; break;
        }
   node.addEventListener("focus", function() {

        draw();
        razmStrel(color, v, lw);
});

   node.addEventListener("blur", draw)



});


</script>

</body>
</html>

MC-XOBAHCK 15.06.2018 19:14

Dilettante_Pro, Спасибо! Я понял идею.
Со стрелками получилось, пришлось ток ещё сначала весь канвас белым прямоугольником залить.

MC-XOBAHCK 15.06.2018 19:37

рони, Спасибо! Я не обновлял страницу и увидел пост поздно, поэтому успел исправить код в топике (так бы новым постом свой код сделал).

А в вашем коде меня смущают вот такие моменты:

[].forEach.call(inp, function(node,i) {....
node.addEventListener("blur", draw)...

Мне ещё до этого уровня как минимум год сидеть за JS, а пока я этого не понимаю : (
Поставлю ваш вариант, надеюсь при расширении приложения проблем не будет, но если что откачу к своему варианту.

рони 15.06.2018 20:20

Цитата:

Сообщение от MC-XOBAHCK
[].forEach.call(inp, function(node,i) {....
node.addEventListener("blur", draw)...

тоже самое(почти) что

Цитата:

for (let i = 0; i < inp.length; i++) {
inp[i].addEventListener
node это inp[i].

рони 15.06.2018 20:22

MC-XOBAHCK,
[].forEach.call

MC-XOBAHCK 16.06.2018 15:08

После простановки координат для 15 стрелочек (я посчитал), случилась эврика (помидор на голову не падал).
Я понял что мне достаточно всего лишь 3 координаты, чтобы нарисовать стрелочку.

Вывел две таких формулы:
//стрелка вертикально (x, y1, y2):
v = [x,y1, x,y2, (x - 3),(y1 + 30), x,y1, (x + 3),(y1 + 30), (x - 3),(y2 - 30), x,y2, (x + 3),(y2 - 30)]

//стрелка горизонтально (x1, x2, y):
v = [x1,y, x2,y, (x1 + 30),(y - 3), x1,y, (x1 + 30),(y + 3), (x2 - 30),(y - 3), x2,y, (x2 + 30),(y + 3)]

Поэтому решил добавить к стрелкам такие параметры:
size: 'little', 'normal', 'big'
orientation: 'horizontal', 'vertical'

Ориентация - по какой формуле считать координаты. Поставлю на if (orientation == 'horizontal') {...}

Размер - это заменю из формулы значения 3 и 30 (ширина и длина самого указателя стрелки), чтобы на маленьких расстояниях можно было уменьшать, на больших - делать побольше.

MC-XOBAHCK 19.06.2018 11:36

Я идею понял, что можно ещё проще всё сделать. Просидел вчера весь день, но прокачаться до уровня "Говнокодер повелитель стрелочек" не получилось.

Пришёл к тому что теперь нужна только одна точка с координатами x,y.
Ввёл новый параметр - длина стрелочки (в px). Так удобнее. Вторую точку находим через формулу прибавив длину стрелочки к координате x.

Убрал ориентацию (горизонтально/вертикально) и вместо неё добавил rotate в градусах (0-360).
При повороте началось смещение. Как я понял, оно регулируется через трансформ, но пока я к этому не пришёл.

Написал такой онлайн повелитель стрелочек, чтобы понять смещение. Но в нём баг. При указании координат x и y - поведение как будто умножается а не суммируется.
Интерпритатор может оценивать в формуле переменную x2 как помножить на два?

Пока завис на этом баге. До смещения ещё не дошёл, попробовал смотреть поведение меняя параметры через strelka.translate(10, 10); но пока мне баг мешает с этим поработать.
<canvas style="border:1px solid black"></canvas>
<p>Координата X: <input id="x" type="number" min="0" value="0"> Координата Y: <input id="y" type="number" min="0" value="0"></p>
<p>Поворот (Градусы): <input id="deg" type="number" min="0" max="360" value="0"> Длина стрелки (px): <input id="w" type="number" min="0" value="400"></p>

<script>
function risunok() {
    let v = [], lstr, hstr,
        color = 'red',  // цвет стрелочки
        lw = 2,         //толщина линии

        deg = +inputDeg.value,
        x = +inputX.value,
        y = +inputY.value,
        w = +inputWidth.value;

    var canvasRoof = document.querySelector('canvas');
    let strelka = canvasRoof.getContext('2d');
    
    switch (true) { //сами указатели стрелочек - длина и высота (в зависимости от длины стрелочки)
        case (w >= 300):            lstr = 30; hstr = 3; break;
        case (w >= 100 && w <300):  lstr = 20; hstr = 2; break;
        case (w < 100):             lstr = 10; hstr = 2; break;
    }
    
    let x2 = (x + w);//координата x2
    v = [x,y, x2,y, (x + lstr),(y - hstr), x,y, (x + lstr),(y + hstr), (x2 - lstr),(y - hstr), x2,y, (x2 - lstr),(y + hstr)];//координаты
    
    canvasRoof.width = 700;
    canvasRoof.height = 450;

    if (canvasRoof && canvasRoof.getContext) {
        strelka.fillStyle = '#fff';
        strelka.fillRect(0, 0, canvasRoof.width, canvasRoof.height);
        
        strelka.translate(x, y);
        strelka.rotate((Math.PI / 180) * deg); //поворот на .. градусов
        strelka.translate(-x, -y);

        strelka.strokeStyle = color;
        strelka.lineWidth = lw;
        strelka.beginPath();
        strelka.moveTo(v[0], v[1]);
        strelka.lineTo(v[2], v[3]);
        strelka.stroke();

        strelka.beginPath();
        strelka.lineWidth = 1;
        strelka.moveTo(v[4], v[5]);
        strelka.lineTo(v[6], v[7]);
        strelka.lineTo(v[8], v[9]);
        strelka.stroke();
        strelka.fillStyle = color;
        strelka.fill();

        strelka.beginPath();
        strelka.lineWidth = 1;
        strelka.moveTo(v[10], v[11]);
        strelka.lineTo(v[12], v[13]);
        strelka.lineTo(v[14], v[15]);
        strelka.stroke();
        strelka.fillStyle = color;
        strelka.fill();
    }
}

const inputDeg = document.querySelector('#deg');
const inputX = document.querySelector('#x');
const inputY = document.querySelector('#y');
const inputWidth = document.querySelector('#w');

inputDeg.addEventListener('input', () => risunok());
inputX.addEventListener('input', () => risunok());
inputY.addEventListener('input', () => risunok());
inputWidth.addEventListener('input', () => risunok());

risunok();
</script>

Dilettante_Pro 19.06.2018 12:40

MC-XOBAHCK,
Насчет вращения:
Перед и после строки 35 впишите

Код:

    strelka.translate(x,y);
35  strelka.rotate((Math.PI / 180) * deg);
    strelka.translate(-x,-y);


MC-XOBAHCK 19.06.2018 13:07

Dilettante_Pro, СПАСИБО! Код поправил. Теперь при повороте без смещения.
Теперь понял как это работает. Это типо как в CSS с абсолютным позиционированием - топ/лефт 50%, а потом по минус 50% от маржинов и мы по центру.

Dilettante_Pro 19.06.2018 13:14

MC-XOBAHCK,
Просто центр вращения надо задать.

MC-XOBAHCK 19.06.2018 13:16

И баг похоже нашёл.
Поставил
console.log(v);

Результат:
["0", "0", "0400", "0", "030", -3, "0", "0", "030", "03", 370, -3, "0400", "0", 370, "03"]

console.log(typeof v[15]);

Результат: string

Наслаживал... :-?

Dilettante_Pro 19.06.2018 14:00

Цитата:

Сообщение от MC-XOBAHCK
И баг похоже нашёл.

Да, плюсики... Хотел было написать - но вы и сами справились!

MC-XOBAHCK 19.06.2018 14:38

Цитата:

Сообщение от Rise (Сообщение 487595)
Относительно стрелок получается что:
1. Размер: меньше, нормально, больше - это масштаб
2. Ориентация: горизонтально, вертикально - это поворот
3. Появился новый пункт - это перемещение origin

Я вроде два из трёх пункта реализовал (разумеется не без посторонней помощи). Идея правильная.
А вот с масштабированием - наверное в моём случае с размерными стрелками оно не нужно. У меня толщина линии одна и та же, и делать толще её не нужно. Просто при малых размерах сами указатели стрелок меняются (поставил условие через свич на длину стрелки), чтобы допустим при 60px длины стрелки указатели не съехались, а визуально оставалась стрелочка.
Параметры свича я со временем подгоню.

Против функции ничего не имею, если честно - я её пока не понимаю и не могу внедрить. Я вон канвас только пару дней назад для себя открыл.

MC-XOBAHCK 25.06.2018 14:57

Добрый день!
А можете подсказать, возможно ли сделать скачивание canvasa?
В контекстном меню браузера, если навести курсор на канвас и нажать правую кнопку мышки, есть пункт "Сохранить картинку как...".
А можно ли это как то повесить на кнопку?

<canvas style="border:1px solid black"></canvas>
<button id="download">Скачать</button>

<script>
  const canvas = document.querySelector('canvas');
  if (canvas.getContext) {
    var ctx = canvas.getContext('2d');

    ctx.fillRect(25,25,100,100);
    ctx.clearRect(45,45,60,60);
    ctx.strokeRect(50,50,50,50);
  }

// Скачать Canvas
document.querySelector('#download').addEventListener('click', function ( ) {
    //?????
})
</script>

j0hnik 25.06.2018 16:08

<canvas style="border:1px solid black"></canvas>
<a href="" download="my-file-name.png" href="#"><button id="download">Download</button></a>

<script>
  const canvas = document.querySelector('canvas');
  if (canvas.getContext) {
    var ctx = canvas.getContext('2d');

    ctx.fillRect(25,25,100,100);
    ctx.clearRect(45,45,60,60);
    ctx.strokeRect(50,50,50,50);
  }

// Скачать Canvas
document.querySelector('#download').addEventListener('click', function(){
    var dataURL = canvas.toDataURL('image/png');
    document.querySelector('a').href = dataURL;
});
</script>

MC-XOBAHCK 25.06.2018 18:24

j0hnik, спасибо за решение.
Скачал с вашего решения - работает.
У меня же скачивает пустой файл. Может быть причина из за того что у меня на локалке, а не на cервере?

Думал может из за того что у меня канвас генерируется, исправил код на всплытие но не помогло:
<div class="editor">
    <canvas></canvas>
    <a href="" download="my-file-name.png" href="#">
        <button id="download">Скачать</button>
    </a>
</div>

Скрипт:
document.querySelector('.editor').addEventListener('click', function (event) {
    if (event.target.id == 'download') {
        let canvas = event.target.parentNode.parentNode.querySelector('canvas');
        var dataURL = canvas.toDataURL('image/png');
        document.querySelector('a').href = dataURL;
    }
})

j0hnik 26.06.2018 03:43

Цитата:

Сообщение от MC-XOBAHCK
У меня же скачивает пустой файл. Может быть причина из за того что у меня на локалке, а не на cервере?

Это вряд ли. в ФФ тоже не пашет?

MC-XOBAHCK 26.06.2018 10:01

Rise, картинок нет - у меня канвас перед кнопкой скачать, из него нужно сделать картинку (в смысле скачать его как картинку).

j0hnik, в фоксе тоже. При этом контекстным меню браузера скачивается как надо.

Я вот начал console.log расставлять и сразу заметил одну странность.
Ставлю так:
let canvas = document.querySelector('.editor canvas');
console.log(canvas);

в консоле пусто. (??)

Делаю так:
let canvas = event.target.parentNode.parentNode.querySelector('canvas');
console.log(canvas);

В консоль выводит: <canvas width="1081" height="700">

Смотрю HTML в вкладке Elements, там <canvas width="1081" height="700"> (без закрывающего тега), вместо изначального: <canvas></canvas>

Размеры канваса у меня задаются через функцию и зависят от размера окна:
function risunokRoof() {
    let widthWindow = document.documentElement.clientWidth;   //Ширина окна браузера
    
    const canvasRoof = document.querySelector('canvas');
    let ctx = canvasRoof.getContext('2d');
        
    canvasRoof.width = widthWindow - 285;
    canvasRoof.height = 700;

Высоту к размерам окна не привязывал, она даёт NULL. Наверно из за того что у body прописан overflow-y: hidden;, а у родительского для канваса divа заданы стили:
.editor {
    position: fixed;
    margin-left: 280px;
    height: 100vh;
    width: calc(100% - 300px);
}


Может быть причина из за того что после выполнения скрипта у канваса нет закрывающего тега?

Alexandroppolus 26.06.2018 10:59

MC-XOBAHCK,
http://jsfiddle.net/ начертай здесь полный пример, в котором не работает
пока только куски кода, а кто знает, что скрыто между ними )

Dilettante_Pro 26.06.2018 11:54

Цитата:

в консоле пусто. (??)
- ????
<div class="editor">
    <canvas></canvas>
    <a href="" download="my-file-name.png" href="#">
        <button id="download">Скачать</button>
    </a>
</div>
<script>
let canvas = document.querySelector('.editor canvas');
console.log(canvas);
alert(canvas.tagName);
</script>

MC-XOBAHCK 26.06.2018 14:57

Все кто писал или просто читал пытаясь вникнуть в мою проблему, вы извините меня пожалуйста, что отнял ваше время.
j0hnik сразу показал рабочий вариант. Это я криворукий говнокодер.
Добавил id к тегу <a> - заработало.

Хотел ещё пару вопросов спросить без кода про канвас, теперь даже не знаю стоит ли.
А ещё меня занесло в библиотеку THREE.js. Наверно за вопросы о ней меня вообще забанят.

Вы извините и огромное всем вам спасибо за помощь.

Alexandroppolus 26.06.2018 15:22

Цитата:

Сообщение от MC-XOBAHCK
Добавил id к тегу <a> - заработало.

все твои беды от того, что не ведаешь браузерный дебаггер

MC-XOBAHCK 26.06.2018 16:11

Цитата:

Сообщение от Alexandroppolus (Сообщение 488404)
все твои беды от того, что не ведаешь браузерный дебаггер

Это да, точно подмечено. Сейчас начну осваивать дебаггер. Когда то смотрел уроки на ютубе, но тогда ничевошеньки не понимал и мог обращаться к элементам только через $ jQuery. Сейчас засяду.
Я в VS Code всегда мечтал освоить дебагер, попробую разобраться по буржуйским урокам.


Часовой пояс GMT +3, время: 08:49.