Помогите понять 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,
<!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>
|
Dilettante_Pro, Спасибо! Я понял идею.
Со стрелками получилось, пришлось ток ещё сначала весь канвас белым прямоугольником залить. |
рони, Спасибо! Я не обновлял страницу и увидел пост поздно, поэтому успел исправить код в топике (так бы новым постом свой код сделал).
А в вашем коде меня смущают вот такие моменты: [].forEach.call(inp, function(node,i) {.... node.addEventListener("blur", draw)... Мне ещё до этого уровня как минимум год сидеть за JS, а пока я этого не понимаю : ( Поставлю ваш вариант, надеюсь при расширении приложения проблем не будет, но если что откачу к своему варианту. |
Цитата:
Цитата:
|
MC-XOBAHCK,
[].forEach.call |
После простановки координат для 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 (ширина и длина самого указателя стрелки), чтобы на маленьких расстояниях можно было уменьшать, на больших - делать побольше. |
Я идею понял, что можно ещё проще всё сделать. Просидел вчера весь день, но прокачаться до уровня "Говнокодер повелитель стрелочек" не получилось.
Пришёл к тому что теперь нужна только одна точка с координатами 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>
|
MC-XOBAHCK,
Насчет вращения: Перед и после строки 35 впишите Код:
strelka.translate(x,y); |
Dilettante_Pro, СПАСИБО! Код поправил. Теперь при повороте без смещения.
Теперь понял как это работает. Это типо как в CSS с абсолютным позиционированием - топ/лефт 50%, а потом по минус 50% от маржинов и мы по центру. |
MC-XOBAHCK,
Просто центр вращения надо задать. |
И баг похоже нашёл.
Поставил 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 Наслаживал... :-? |
Цитата:
|
Цитата:
А вот с масштабированием - наверное в моём случае с размерными стрелками оно не нужно. У меня толщина линии одна и та же, и делать толще её не нужно. Просто при малых размерах сами указатели стрелок меняются (поставил условие через свич на длину стрелки), чтобы допустим при 60px длины стрелки указатели не съехались, а визуально оставалась стрелочка. Параметры свича я со временем подгоню. Против функции ничего не имею, если честно - я её пока не понимаю и не могу внедрить. Я вон канвас только пару дней назад для себя открыл. |
Добрый день!
А можете подсказать, возможно ли сделать скачивание 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>
|
<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>
|
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;
}
})
|
Цитата:
|
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);
}
Может быть причина из за того что после выполнения скрипта у канваса нет закрывающего тега? |
MC-XOBAHCK,
http://jsfiddle.net/ начертай здесь полный пример, в котором не работает пока только куски кода, а кто знает, что скрыто между ними ) |
Цитата:
<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>
|
Все кто писал или просто читал пытаясь вникнуть в мою проблему, вы извините меня пожалуйста, что отнял ваше время.
j0hnik сразу показал рабочий вариант. Это я криворукий говнокодер. Добавил id к тегу <a> - заработало. Хотел ещё пару вопросов спросить без кода про канвас, теперь даже не знаю стоит ли. А ещё меня занесло в библиотеку THREE.js. Наверно за вопросы о ней меня вообще забанят. Вы извините и огромное всем вам спасибо за помощь. |
Цитата:
|
Цитата:
Я в VS Code всегда мечтал освоить дебагер, попробую разобраться по буржуйским урокам. |
| Часовой пояс GMT +3, время: 18:12. |