Skip to content

Instantly share code, notes, and snippets.

@motsu0
Last active February 7, 2023 07:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save motsu0/21a881191f308679412ee65587337bd2 to your computer and use it in GitHub Desktop.
Save motsu0/21a881191f308679412ee65587337bd2 to your computer and use it in GitHub Desktop.
.img-sample{
width: 150px;
}
/* */
.area-work{
text-align: center;
}
#area-img{
display: inline-block;
position: relative;
box-shadow: 0 0 0 1px #ccc;
overflow: hidden;
}
.monologue{
padding: 20px 0 20px 20px;
position: absolute;
top: 0;
left: 0;
border: 2px solid #000;
background-color: #fff;
line-height: 1.2em;
user-select: none;
cursor: move;
font-size: 16px;
letter-spacing: 20px;
font-family: 'Noto Sans JP', sans-serif;
}
.letter-mini{
display: inline-block;
transform: translate(5%, -10%);
}
#img-main{
display: block;
width: 100%;
}
.area-file{
margin: 16px 0;
text-align: center;
}
#label-file{
display: inline-block;
padding: 8px 16px;
border: 1px solid #aaa;
border-radius: 4px;
background-color: #eee;
cursor: pointer;
}
#label-file.label-file--dragover{
background-color: #ddd;
}
#label-file:hover{
background-color: #ddd;
}
#message{
margin-top: 12px;
color: #ee0000;
}
/* */
.control__menu{
display: flex;
column-gap: 4px;
}
.control__menu__item{
flex-grow: 1;
background-color: #ddd;
cursor: pointer;
}
.control__menu__item.on{
border-bottom: none;
background-color: #2ECC71;
color: white;
}
.control__block{
height: 600px;
border: 2px solid #2ECC71;
overflow-y: auto;
}
/* */
#textarea-input{
margin-top: 24px;
}
.control__textarea{
display: block;
width: 80%;
height: 100px;
margin: 12px auto;
resize: vertical;
}
/* */
.settings{
padding: 12px 0;
}
.settings:nth-child(n+2){
border-top: 1px dashed #2ECC71;
}
.settings__row{
margin: 8px 0;
}
.input-number{
width: 60px;
}
.bt-del{
border: none;
background-color: #ee0000;
color: #fff;
}
/* */
#bt-dl{
margin-top: 24px;
}
/* */
.bt-control{
display: block;
padding: 4px 8px;
margin: 12px auto;
cursor: pointer;
}
/* */
.s-hide{
display: none;
}
<div class="area-work">
<div id="area-img">
<img src="" alt="" id="img-main" draggable="false">
</div>
<div class="area-file">
<label for="input-file" id="label-file">画像を読み込む</label>
<input type="file" class="s-hide" id="input-file" accept="image/*">
<div id="message" class="s-hide">画像を選択してください。</div>
</div>
<div class="control">
<div class="control__menu">
<div class="control__menu__item on">新規</div>
<div class="control__menu__item">編集</div>
<div class="control__menu__item">出力</div>
</div>
<div class="control__block control-new">
<textarea id="textarea-input" class="control__textarea" placeholder="追加したい文章(全角文字推奨)"></textarea>
<button id="bt-add" class="bt-control">モノローグを追加</button>
</div>
<div id="control-edit" class="control__block s-hide">
</div>
<div class="control__block control-edit s-hide">
<button id="bt-dl" class="bt-control">画像を保存</button>
</div>
</div>
</div>
<div class="settings s-hide" id="settings-origin">
<textarea class="control__textarea"></textarea>
<button class="bt-control bt-textedit">テキスト更新</button>
<div class="settings__row">
文字サイズ
<input type="number" class="input-fontsize input-number" min="0" value="16">px
</div>
<div class="settings__row">
文字の色
<input type="color" class="input-color" value="#000000">
</div>
<div class="settings__row">
フォント
<select class="sel-font"></select>
</div>
<div class="settings__row">
枠の太さ
<input type="number" class="input-border-width input-number" min="0" value="2">px
</div>
<div class="settings__row">
枠の色
<input type="color" class="input-border-color" value="#000000">
</div>
<div class="settings__row">
背景色
<input type="color" class="input-bg-color" value="#ffffff">
</div>
<div class="settings__row">
行間
<input type="number" class="input-between input-number" min="0" value="20">px
</div>
<div class="settings__row">
余白
<input type="number" class="input-padding input-number" min="0" value="20">px
</div>
<button class="bt-del bt-control">削除</button>
</div>
<script src="/js/now-loading.js"></script>
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js" integrity="sha256-6H5VB5QyLldKH9oMFUmjxw2uWpPZETQXpCkBaDjquMs=" crossorigin="anonymous"></script>
const nowloading = new nowLoading();
const el_area_img = document.getElementById('area-img');
const el_img_main = document.getElementById('img-main');
const el_input_file = document.getElementById('input-file');
const el_label_file = document.getElementById('label-file');
const el_message = document.getElementById('message');
const els_monologue = document.getElementsByClassName('monologue');
const el_textarea_input = document.getElementById('textarea-input');
const els_control__menu__item = document.getElementsByClassName('control__menu__item');
const els_control_block = document.getElementsByClassName('control__block');
const bt_add = document.getElementById('bt-add');
const el_control_edit = document.getElementById('control-edit');
const el_settings_origin = document.getElementById('settings-origin');
const bt_dl = document.getElementById('bt-dl');
const fontset = [
{
name: 'Noto Sans JP',
css: '\'Noto Sans JP\', sans-serif'
},
{
name: 'Noto Serif JP',
css: '\'Noto Serif JP\', serif'
},
{
name: 'DotGothic16',
css: '\'DotGothic16\', sans-serif'
},
{
name: 'M PLUS 1p',
css: '\'M PLUS 1p\', sans-serif'
},
{
name: 'RocknRoll One',
css: '\'RocknRoll One\', sans-serif'
},
{
name: 'Sawarabi Gothic',
css: '\'Sawarabi Gothic\', sans-serif'
},
{
name: 'Shippori Mincho',
css: '\'Shippori Mincho\', serif'
},
{
name: 'Zen Kurenaido',
css: '\'Zen Kurenaido\', sans-serif'
},
{
name: 'Zen Maru Gothic',
css: '\'Zen Maru Gothic\', sans-serif'
}
];
const array_replace = [
['、','︑'],
['。','︒'],
['(','︵'],
[')','︶'],
['{','︷'],
['}','︸'],
['〔','︹'],
['〕','︺'],
['【','︻'],
['】','︼'],
['《','︽'],
['》','︾'],
['〈','︿'],
['〉','﹀'],
['「','﹁'],
['」','﹂'],
['『','﹃'],
['』','﹄'],
['[','﹇'],
[']','﹈'],
['―','︱'],
['ー','丨'],
['‥','︰'],
['…','︙']
];
const array_letter_mini = [
'っ','ッ',
'ゃ','ャ',
'ゅ','ュ',
'ょ','ョ',
'ぁ','ァ',
'ぃ','ィ',
'ぅ','ゥ',
'ぇ','ェ',
'ぉ','ォ',
'ゎ','ヮ'
];
let width_image = 1080;
init();
function init(){
nowloading.start();
//リスナ設定
[...els_control__menu__item].forEach((element,i,ar)=>{
element.addEventListener('click',()=>{
ar.forEach(el=>{
el.classList.remove('on');
});
element.classList.add('on');
[...els_control_block].forEach(el=>{
el.classList.add('s-hide');
});
els_control_block[i].classList.remove('s-hide');
});
});
bt_add.addEventListener('click',addMonologue);
bt_dl.addEventListener('click',downloadImg);
el_input_file.addEventListener('change',inputFile);
el_label_file.addEventListener('dragover',fileDragover);
el_label_file.addEventListener('drop',fileDrop);
el_label_file.addEventListener('dragleave',fileDragEnd);
//画像読み込み
el_img_main.addEventListener('load',()=>{
el_area_img.style.width = '';
width_image = el_img_main.naturalWidth;
const height = el_img_main.clientHeight;
const rate_h = height/window.screen.height;
if(rate_h>.8){
el_area_img.style.width = (80/rate_h)+'%';
}
});
//フォント
const el_sel_font = el_settings_origin.getElementsByClassName('sel-font')[0];
fontset.forEach(val=>{
const el_option = document.createElement('option');
el_option.textContent = val.name;
el_option.value = val.css;
el_sel_font.appendChild(el_option);
});
//初期化終了
nowloading.stop();
}
// fileDragover => fileDrop => checkFile
// ↓ inputFile ↗
// fileDragEnd
function fileDragover(e){
e.preventDefault();
e.target.classList.add('label-file--dragover');
}
function fileDrop(e){
e.preventDefault();
nowloading.start();
e.target.classList.remove('label-file--dragover');
const files = e.dataTransfer.files;
if(files.length===0){
nowloading.stop();
return;
}
checkFile(files[0]);
}
function fileDragEnd(e){
e.target.classList.remove('label-file--dragover');
}
function inputFile(e){
nowloading.start();
el_message.classList.add('s-hide');
const files = e.target.files;
if(files.length===0){
nowloading.stop();
return
}
checkFile(files[0]);
}
function checkFile(file){
if(file.type.indexOf('image')===-1){
nowloading.stop();
el_message.classList.remove('s-hide');
return;
}
const reader = new FileReader();
reader.onload = ev=>{
el_img_main.src = ev.target.result;
nowloading.stop();
}
reader.readAsDataURL(file);
}
function addMonologue(){
//文字取得
const input_text = el_textarea_input.value;
if(input_text.length === 0) return;
const html_monologue = makeHtml(input_text);
//追加要素作成
const el_monologue = document.createElement('div');
el_monologue.classList.add('monologue');
el_monologue.innerHTML = html_monologue;
el_area_img.appendChild(el_monologue);
//ドラッグアンドドロップ処理
let x_pos = 0;
let y_pos = 0;
let x_cursor;
let y_cursor;
let is_dragging = false;
el_monologue.addEventListener('mousedown',dragStart);
el_monologue.addEventListener('touchstart',dragStart);
el_area_img.addEventListener('mousemove',drag);
el_area_img.addEventListener('touchmove',drag);
el_monologue.addEventListener('mouseup',dragEnd);
el_monologue.addEventListener('touchend',dragEnd);
el_area_img.addEventListener('mouseleave',dragEnd);
el_area_img.addEventListener('touchleave',dragEnd);
//編集機能追加
const el_settings = el_settings_origin.cloneNode(true);
el_settings.id = '';
el_settings.classList.remove('s-hide');
//
const el_input_text = el_settings.getElementsByClassName('control__textarea')[0];
el_input_text.value = el_textarea_input.value;
const bt_textedit = el_settings.getElementsByClassName('bt-textedit')[0];
bt_textedit.addEventListener('click',changeText);
const el_input_fontsize = el_settings.getElementsByClassName('input-fontsize')[0];
el_input_fontsize.addEventListener('input',()=>{changeFontsize(el_input_fontsize)});
const el_input_color = el_settings.getElementsByClassName('input-color')[0];
el_input_color.addEventListener('input',()=>{changeColor(el_input_color)});
const el_sel_font = el_settings.getElementsByClassName('sel-font')[0];
el_sel_font.addEventListener('change',changeFont);
const el_input_border_width = el_settings.getElementsByClassName('input-border-width')[0];
el_input_border_width.addEventListener('input',()=>{changeBorderWidth(el_input_border_width)});
const el_input_border_color = el_settings.getElementsByClassName('input-border-color')[0];
el_input_border_color.addEventListener('input',()=>{changeBorderColor(el_input_border_color)});
const el_input_bg_color = el_settings.getElementsByClassName('input-bg-color')[0];
el_input_bg_color.addEventListener('input',()=>{changeBgColor(el_input_bg_color)});
const el_input_between = el_settings.getElementsByClassName('input-between')[0];
el_input_between.addEventListener('input',changePadding);
const el_input_padding = el_settings.getElementsByClassName('input-padding')[0];
el_input_padding.addEventListener('input',changePadding);
const bt_del = el_settings.getElementsByClassName('bt-del')[0];
bt_del.addEventListener('click',deleteMonologue);
//
el_control_edit.appendChild(el_settings);
//後処理
el_textarea_input.value = '';
//サブ関数
function makeHtml(text){
const array_line = text.split(/\n/);
const width = array_line.length;
const height = array_line.map(v=>v.length).reduce((p,c)=>Math.max(p,c));
const array_input = array_line.map(v=>v.split(''));
const array_output = (new Array(height)).fill('').map(v=>(new Array(width).fill('')));
for(r=0;r<height;r++){
for(c=0;c<width;c++){
const val = array_input[c][r];
if(val==undefined){
array_output[r][width-c] = ' ';
}else if(array_letter_mini.includes(val)){
const el_span = document.createElement('span');
el_span.classList.add('letter-mini');
el_span.textContent = val;
array_output[r][width-c] = el_span.outerHTML;
}else{
let val_replaced = val;
array_replace.forEach(set=>{
val_replaced = val_replaced.replace(set[0],set[1]);
});
array_output[r][width-c] = escHtml(val_replaced);
}
}
}
return array_output.map(line=>line.join('')).join('<br>');
}
//サブ関数 ドラッグ
function dragStart(e){
is_dragging = true;
const event = (()=>{
if(e.type==='mousedown'){
return e;
}else{
return e.changedTouches[0];
}
})();
x_cursor = event.pageX;
y_cursor = event.pageY;
}
function drag(e){
e.preventDefault();
if(!is_dragging) return;
const event = (()=>{
if(e.type==='mousemove'){
return e;
}else{
return e.changedTouches[0];
}
})();
x_pos += event.pageX - x_cursor;
y_pos += event.pageY - y_cursor;
x_cursor = event.pageX;
y_cursor = event.pageY;
el_monologue.style.transform = `translate(${x_pos}px,${y_pos}px)`;
}
function dragEnd(){
is_dragging = false;
}
//サブ関数 編集
function changeText(){
el_monologue.innerHTML = makeHtml(el_input_text.value);
}
function changeFontsize(el_input){
const value = (()=>{
let temp = Number(el_input.value);
if(temp<4){
temp = 4;
}
return temp;
})();
el_monologue.style.fontSize = value+'px';
}
function changeColor(el_input){
el_monologue.style.color = el_input.value;
}
function changeFont(){
el_monologue.style.fontFamily = el_sel_font.value;
}
function changeBorderWidth(el_input){
const value = (()=>{
let temp = Number(el_input.value);
if(temp<1){
temp = 1;
}
return temp;
})();
el_monologue.style.borderWidth = value+'px';
}
function changeBorderColor(el_input){
el_monologue.style.borderColor = el_input.value;
}
function changeBgColor(el_input){
el_monologue.style.backgroundColor = el_input.value;
}
function changePadding(){
const value_padding = (()=>{
let temp = Number(el_input_padding.value);
if(temp<0){
temp = 0;
}
return temp;
})();
const value_between = (()=>{
let temp = Number(el_input_between.value);
if(temp<0){
temp = 0;
}
return temp;
})();
const gap = value_padding - value_between;
if(gap>=0){
el_monologue.style.padding = `${value_padding}px ${gap}px ${value_padding}px ${value_padding}px`;
}else{
el_monologue.style.padding = `${value_padding}px 0px ${value_padding}px ${value_between}px`;
}
el_monologue.style.letterSpacing = value_between + 'px';
}
function deleteMonologue(){
el_settings.remove();
el_monologue.remove();
}
}
function downloadImg(){
nowloading.start();
setTimeout(()=>{
const scale = (()=>{
let temp = width_image / el_img_main.clientWidth;
if(temp<1) temp = 1;
return temp;
})();
html2canvas(el_area_img,{
scale: scale
}).then(canvas => {
canvas.toBlob(blob=>{
const alink = document.createElement('a');
alink.href = URL.createObjectURL(blob);
alink.download = 'img-monologue.png';
alink.click();
URL.revokeObjectURL(blob);
nowloading.stop();
});
});
},1);
}
function escHtml(str){
str = str.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
return str;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment