SOURCE

console 命令行工具 X clear

                    
>
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);
}