console
const VERTICAL = 'vertical';
const HORIZONTAL = 'horizontal';
const HIDDEN = 'hidden';
const LINE = 'line';
const PREVIEW_SIZE = 320;
const lines = {};
let currentLine = null;
let currentMode = null;
let image = null;
const handleFile = file => {
if (file && /^image\//.test(file.type)) {
attachmentContainer.classList.add(HIDDEN);
actionbarContainer.classList.remove(HIDDEN);
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
const dataUrl = reader.result;
image = new Image();
image.onload = () => {
lineContainer.style.width = `${image.width}px`;
lineContainer.style.height = `${image.height}px`;
lines[`${VERTICAL}-${image.width}`] = 1;
lines[`${HORIZONTAL}-${image.height}`] = 1;
lines[`${VERTICAL}-0`] = 1;
lines[`${HORIZONTAL}-0`] = 1;
};
image.src = dataUrl;
lineContainer.appendChild(image);
};
}
};
attachmentContainer.ondragover = evt => {
evt.preventDefault();
};
attachmentContainer.ondrop = evt => {
evt.preventDefault();
handleFile(evt.dataTransfer.files[0]);
};
attachmentInput.onchange = () => {
handleFile(attachmentInput.files[0]);
};
addVerticalLineBtn.onclick = addHorizontalLineBtn.onclick = evt => {
currentMode = evt.target.dataset.value;
currentLine = document.createElement('div');
currentLine.title = 'Double click to delete me';
currentLine.classList.add(currentMode, LINE);
actionbarContainer.classList.add(HIDDEN);
lineContainer.appendChild(currentLine);
};
lineContainer.onmousemove = evt => {
if (currentLine && evt.target !== currentLine) {
const { offsetX, offsetY } = evt;
if (currentMode === VERTICAL) {
currentLine.style.left = `${offsetX}px`;
} else if (currentMode === HORIZONTAL) {
currentLine.style.top = `${offsetY}px`;
}
}
};
lineContainer.onclick = evt => {
if (currentLine) {
if (currentMode === VERTICAL) {
lines[`${VERTICAL}-${currentLine.offsetLeft}`] = 1;
} else if (currentMode === HORIZONTAL) {
lines[`${HORIZONTAL}-${currentLine.offsetTop}`] = 1;
}
currentLine = currentMode = null;
actionbarContainer.classList.remove(HIDDEN);
}
};
lineContainer.ondblclick = evt => {
if (!currentLine && evt.target.classList.contains(LINE)) {
if (evt.target.classList.contains(VERTICAL)) {
delete lines[`${VERTICAL}-${evt.target.offsetLeft}`];
} else if (evt.target.classList.contains(HORIZONTAL)) {
delete lines[`${HORIZONTAL}-${evt.target.offsetTop}`];
}
lineContainer.removeChild(evt.target);
}
};
previewBtn.onclick = () => {
lineContainer.classList.add(HIDDEN);
actionbarContainer.classList.add(HIDDEN);
previewContainer.classList.remove(HIDDEN);
const links = Array.from(previewContainer.childNodes).filter(n => !!n.href);
links.forEach(n => previewContainer.removeChild(n));
const points = Object.keys(lines).reduce((prev, cur) => {
const [mode, value] = cur.split('-');
prev[mode] = prev[mode] || [];
prev[mode].push(parseInt(value));
return prev;
}, {});
const asc = (a, b) => a - b;
points[VERTICAL].sort(asc);
points[HORIZONTAL].sort(asc);
const exportParams = [];
for (let j = 1; j < points[HORIZONTAL].length; j++) {
for (let i = 1; i < points[VERTICAL].length; i++) {
const sx = points[VERTICAL][i - 1];
const sy = points[HORIZONTAL][j - 1];
const sWidth = points[VERTICAL][i] - points[VERTICAL][i - 1];
const sHeight = points[HORIZONTAL][j] - points[HORIZONTAL][j - 1];
const canvas = document.createElement('canvas');
canvas.width = sWidth;
canvas.height = sHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(image, sx, sy, sWidth, sHeight, 0, 0, sWidth, sHeight);
const previewImage = new Image();
const dataUrl = canvas.toDataURL('image/png', 1)
previewImage.src = dataUrl;
previewImage.width = sWidth > sHeight ? PREVIEW_SIZE : PREVIEW_SIZE * sWidth / sHeight;
previewImage.height = sWidth > sHeight ? PREVIEW_SIZE * sHeight / sWidth : PREVIEW_SIZE;
const item = document.createElement('a');
item.href = dataUrl;
item.download = `image_${j}_${i}.png`;
item.appendChild(previewImage);
previewContainer.appendChild(item);
}
}
};
exportAll.onclick = () => {
Array.from(previewContainer.childNodes).filter(n => !!n.href).map(n => n.click());
};
backToEditor.onclick = () => {
previewContainer.classList.add(HIDDEN);
lineContainer.classList.remove(HIDDEN);
actionbarContainer.classList.remove(HIDDEN);
};
<label id="attachmentContainer" class="attachment-container">
<span>Click me to upload image</span>
<input id="attachmentInput" type="file" accept="image/*" />
</label>
<div id="lineContainer" class="line-container"></div>
<div id="actionbarContainer" class="actionbar-container hidden">
<button id="addVerticalLineBtn" data-value="vertical">Vertical Line</button>
<button id="addHorizontalLineBtn" data-value="horizontal">Horizontal Line</button>
<button id="previewBtn">Preview</button>
</div>
<div id="previewContainer" class="preview-container hidden">
<button id="exportAll">Export All</button>
<button id="backToEditor">Back</button>
<div class="br-line"></div>
</div>
html {
background-color: rgba(0, 0, 0, .6);
color: rgba(255, 255, 255, .9);
}
html,
body {
margin: 0;
padding: 0;
}
.hidden {
display: none !important;
}
.br-line {
width: 100%;
}
button {
height: 32px;
margin: 10px 0 0 10px;
background: none;
background-color: rgb(255, 255, 255);
border: solid 1px rgba(0, 0, 0, .3);
border-radius: 3px;
color: rgba(0, 0, 0, .6);
cursor: pointer;
}
.attachment-container {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: hidden;
cursor: pointer;
padding-top: 200px;
text-align: center;
font-size: 48px;
}
.attachment-container>input {
position: absolute;
top: -1000px;
}
.actionbar-container {
position: fixed;
top: 10px;
left: 10px;
}
.line-container {
position: relative;
overflow: hidden;
}
.line {
background-color: rgb(72, 226, 67);
position: absolute;
cursor: pointer;
}
.vertical {
width: 1px;
height: 100%;
top: 0;
bottom: 0;
transform: translateX(-0.5px);
}
.horizontal {
width: 100%;
height: 1px;
left: 0;
right: 0;
transform: translateY(-0.5px);
}
.preview-container {
display: flex;
align-items: flex-start;
justify-content: flex-start;
flex-wrap: wrap;
}
.preview-container>a {
width: 320px;
height: 320px;
margin: 10px 0 0 10px;
display: flex;
align-items: center;
justify-content: center;
background-color: rgb(255, 255, 255);
}