EditorCanvas.vue (6462B)
1 <template> 2 <div> 3 <canvas ref="canvasElement"></canvas> 4 </div> 5 </template> 6 7 <script setup> 8 import { ref, onMounted } from 'vue'; 9 import { Canvas, FabricImage, StaticCanvas } from 'fabric'; 10 11 const canvasElement = ref(null); 12 let fabricCanvas = null; 13 14 onMounted(() => { 15 if (canvasElement.value) { 16 fabricCanvas = new Canvas(canvasElement.value, { 17 width: 1, 18 height: 1, 19 backgroundColor: 'transparent' 20 }); 21 console.log('Fabric.jsのCanvasを初期化しました'); 22 } else { 23 console.error('onMounted: canvasElement が見つかりません'); 24 } 25 }); 26 27 const addOshiImage = (imageUrl) => { 28 if (!fabricCanvas) return; 29 console.log('addOshiImage が呼ばれました'); 30 31 const imgElement = new Image(); 32 imgElement.onload = () => { 33 console.log('推し画像の読み込み成功 (Image.onload)'); 34 const fabricImg = new FabricImage(imgElement); 35 36 // Canvasのサイズに対して、いい感じのサイズに調整 37 // (例: Canvasの幅の 1/3 サイズにする) 38 const scale = (fabricCanvas.width * 0.33) / imgElement.naturalWidth; 39 40 fabricImg.set({ 41 left: 100, 42 top: 100, 43 scaleX: scale, 44 scaleY: scale, 45 }); 46 47 fabricCanvas.add(fabricImg); 48 fabricCanvas.renderAll(); 49 console.log('「推し」画像を追加しました'); 50 }; 51 imgElement.onerror = (err) => { /* ... (省略) ... */ }; 52 imgElement.src = imageUrl; 53 }; 54 55 const resizeAndSetBackground = (dataUrl, imgElement, containerWidth) => { 56 if (!fabricCanvas) { 57 console.error('resizeAndSetBackground: fabricCanvas が初期化されていません'); 58 return; 59 } 60 console.log('resizeAndSetBackground が呼ばれました'); 61 62 // 1. 新しいCanvasのサイズを計算 63 const imgRatio = imgElement.naturalHeight / imgElement.naturalWidth; 64 65 // 親コンテナの幅(containerWidth)をそのままCanvasの幅として使用します 66 const newWidth = containerWidth; 67 const newHeight = newWidth * imgRatio; 68 69 // 2. Fabric.jsのCanvasにサイズをセット 70 fabricCanvas.setDimensions({ 71 width: newWidth, 72 height: newHeight 73 }); 74 75 // 3. <img> から FabricImage を作成 76 const fabricImg = new FabricImage(imgElement); 77 78 // 4. 背景としてセット 79 fabricCanvas.backgroundColor = 'transparent'; // 以前の背景色をクリア 80 fabricCanvas.backgroundImage = fabricImg; 81 82 // 5. 画像をCanvasの幅にフィットさせる 83 fabricImg.scaleToWidth(newWidth); 84 85 // 6. 変更をCanvasに反映 86 fabricCanvas.renderAll(); 87 88 console.log(`Canvasをリサイズしました: ${newWidth} x ${newHeight}`); 89 }; 90 91 const deleteActiveObject = () => { 92 if (!fabricCanvas) return; 93 const activeObject = fabricCanvas.getActiveObject(); 94 if (activeObject) { 95 fabricCanvas.remove(activeObject); 96 fabricCanvas.renderAll(); 97 } 98 }; 99 100 const exportOriginalSizeDataURL = (originalImageElement) => { 101 try { 102 if (!fabricCanvas || !originalImageElement) { 103 console.error('exportOriginalSizeDataURL: 引数が不足しています'); 104 return null; 105 } 106 107 // 1. オリジナル解像度 108 const originalWidth = originalImageElement.naturalWidth; 109 const originalHeight = originalImageElement.naturalHeight; 110 111 // 2. 現在の表示サイズ 112 const currentWidth = fabricCanvas.width; 113 114 // 3. スケーリング比率 115 const ratio = originalWidth / currentWidth; 116 117 if (ratio <= 0 || !isFinite(ratio)) { 118 console.error('無効なスケーリング比率です', ratio); 119 return null; 120 } 121 122 // 4. メモリ上に元サイズの StaticCanvas を作成 123 const staticCanvas = new StaticCanvas(null, { 124 width: originalWidth, 125 height: originalHeight 126 }); 127 128 // 5. 背景画像を手動で「再構築」 129 const bgImage = fabricCanvas.backgroundImage; 130 if (bgImage) { 131 // getElement() で <img> 要素を取得 132 const bgImgElement = bgImage.getElement(); 133 const newBgScale = bgImage.scaleX * ratio; 134 135 // <img> 要素から新しい FabricImage を作成 136 const newBg = new FabricImage(bgImgElement, { 137 scaleX: newBgScale, 138 scaleY: newBgScale 139 }); 140 staticCanvas.backgroundImage = newBg; 141 } 142 143 // 6. オブジェクト(推し)を個別に「再構築」 144 const objects = fabricCanvas.getObjects(); 145 if (objects.length > 0) { 146 objects.forEach(obj => { 147 // 7. <img> 要素を取得 (FabricImage のみ) 148 if (obj.type === 'image' && obj.getElement) { 149 const oshiImgElement = obj.getElement(); 150 151 // 8. <img> とスケーリング済みのプロパティから新しい FabricImage を作成 152 const newOshi = new FabricImage(oshiImgElement, { 153 // スケーリングするプロパティ 154 left: obj.left * ratio, 155 top: obj.top * ratio, 156 scaleX: obj.scaleX * ratio, 157 scaleY: obj.scaleY * ratio, 158 159 // そのままコピーするプロパティ 160 angle: obj.angle, 161 flipX: obj.flipX, 162 flipY: obj.flipY, 163 skewX: obj.skewX, 164 skewY: obj.skewY, 165 originX: obj.originX, 166 originY: obj.originY, 167 // ... (他にも必要なプロパティがあればここに追加) 168 }); 169 170 // 9. StaticCanvas に追加 171 staticCanvas.add(newOshi); 172 } 173 }); 174 } 175 176 // 10. 最終描画 177 staticCanvas.renderAll(); 178 179 // 11. 高解像度のData URLを生成 180 const dataUrl = staticCanvas.toDataURL({ 181 format: 'png', 182 quality: 1.0 183 }); 184 185 // 12. メモリ解放 186 staticCanvas.dispose(); 187 188 return dataUrl; // ★ 成功パス 189 190 } catch (error) { 191 console.error('exportOriginalSizeDataURL 内部でエラー:', error); 192 return null; // ★ 失敗パス 193 } 194 }; 195 196 defineExpose({ 197 resizeAndSetBackground, 198 addOshiImage, 199 deleteActiveObject, 200 exportOriginalSizeDataURL 201 }); 202 </script> 203 204 <style scoped> 205 /* 206 <canvas> タグを囲むラッパー(自動生成)に 207 スタイルを適用するため :deep() を使います 208 */ 209 :deep(.canvas-container) { 210 /* 211 親要素 (App.vueの .canvas-wrapper) の 212 サイズに追従するようにします 213 */ 214 max-width: 100%; 215 max-height: 100%; 216 217 /* スマホ画面で縮小表示されたときもくっきりさせる */ 218 image-rendering: -webkit-optimize-contrast; 219 image-rendering: crisp-edges; 220 } 221 </style>