\ 写真L版で作れる /
写真で作るフォトカードメーカー
手元の写真で、記念カード・ごほうびカード・視覚支援カードを作れます。
コンビニのL版印刷にも使いやすい無料ツールです。


フォトカードを作る

\ ちょっとひと息 /
🔮 今日の子育ておみくじ
今日の子育て運をちょこっと占ってみよう♪
どんなメッセージが出るかな?


今日のおみくじを引く

\ 困りごとに合わせて探せます /
📚 無料教材をさがす
年齢・教科・困りごとから
視覚支援カードや無料教材を探せます。


教材をさがす →

\ 遊びながら足し算を練習 /
🍒 小学1年生の足し算ガチャ
足し算に正解すると、ガチャを1回まわせます!
10までの足し算・さくらんぼ計算・間違えた問題の復習ができます。


無料で足し算ガチャを遊ぶ →

\ 遊びながら引き算を練習 /
🌳 小学1年生の引き算ガチャ
引き算に正解すると、ガチャを1回まわせます!
10までの引き算・くり下がり・間違えた問題の復習ができます。


無料で引き算ガチャを遊ぶ →

\ 宿題を楽しくスタート /
🎣 宿題フィッシング
好きな海の生き物を釣って、
最初に取り組む宿題を楽しく決めよう!


魚を釣ってみる →

\ 好きな言葉でカードを作ろう /
📝 宿題サポートカードメーカー
「まず・つぎ・おわり」や切り取りカードへ、
好きな言葉を入力して、そのままA4で印刷できます。


無料でカードを作る →

登録不要・入力内容は端末内だけに保存

\ 親子でちょこっと遊ぼう /
🎲 親子でチャレンジ!
ボタンを押して、出てきたミッションに挑戦!
道具なし・1~5分で、親子や兄弟一緒に遊べます。


今日のミッションに挑戦する →

\ 紙とえんぴつで遊ぼう /
🎨 なにができるかな? おえかきポン!
指示を一つずつ見ながら、100種類の絵を完成させよう!
分からないときは、手描きのお手本も見られます。


おえかきポン!で遊ぶ →

\ がんばるあなたへ /
☕ 今日のひとやすみ
今日のあなたへの言葉と、
すぐにできる小さな息抜きをお届けします。


今日のひとやすみを受け取る →

\ おしたくを冒険にしよう /
🗺️ おしたく探検隊
朝・夜・学校の準備を、冒険のようにひとつずつ!
犬と猫と一緒に、楽しくおしたくを進めよう。


探検に出発する →

\ 文字を選んで言葉を作ろう /
🐶 ことばづくりガチャ 🐱
絵やヒントを見ながら、文字を選んで言葉を完成させよう!
拗音・促音・長音など全149語を、ガチャで楽しく練習できます。


ことばづくりガチャで遊ぶ →

\ 気持ちが伝わる言葉を練習 /
🐶 ことばへんしん! 🐱
「うるさい!」「ずるい!」などの強い言葉を、
自分の気持ちが伝わる言葉に変えてみよう!


ことばへんしん!で遊ぶ →

全60問・登録なし・無料


\ 遊びながら心を育てる /
🔎 きもち探偵
この子は、どんな気持ちかな?
気持ちの違いや、行動の奥にある願いを
親子で楽しく考えてみよう♪
🐶 🔎 🐱


きもち探偵で遊ぶ →

登録・ダウンロード不要


\ ことばを読んで探検しよう /
🧭 道順ことばゲーム
上・下・右・左のことばを読んで、
犬や猫と一緒に道案内や宝探しをしよう!
方向・順番・マスの数を楽しく練習できます。


道順ことばゲームで遊ぶ →

登録なし・無料で遊べます

\ 漢字を楽しく覚えよう /
🐶 小1漢字たんけんマップ 🐱
小学1年生で習う漢字80字を、
仲間・分解・覚え言葉で練習できます。
登録なし・無料です。


漢字たんけんに出発する →

\ 文字の「ちがい」を見つけよう /
🐶 そっくり文字ハンター 🐱
ツ・シ・ン・ソなどの似ている文字や、
書き順・句読点・かぎかっこの位置を楽しく練習!


無料であそぶ

登録なし・幼児〜小学生向け

\ ことばを頭から探してみよう /
🗄️ ことばの引き出し
動物・色・今日の出来事などを、
ヒントを使いながらゆっくり思い出すゲームです。
幼児から小学生まで無料で遊べます。
☀️ 今日のこと 🏫 場所から探す
🔤 音から探す 1️⃣2️⃣3️⃣ 順番に話す


無料で遊ぶ

登録なし・答えの入力なし
(function () { try { const root = document.getElementById('sn-photo-card-maker'); if (!root) return; const fileInput = root.querySelector('#snpcm-image'); const layoutSelect = root.querySelector('#snpcm-layout'); const captionSelect = root.querySelector('#snpcm-caption'); const frameSelect = root.querySelector('#snpcm-frame'); const radiusSelect = root.querySelector('#snpcm-radius'); const textColorSelect = root.querySelector('#snpcm-text-color'); const dateModeSelect = root.querySelector('#snpcm-date-mode'); const textList = root.querySelector('#snpcm-text-list'); const makeButton = root.querySelector('#snpcm-make'); const saveButton = root.querySelector('#snpcm-save'); const printButton = root.querySelector('#snpcm-print'); const canvas = root.querySelector('#snpcm-canvas'); const ctx = canvas.getContext('2d'); let selectedImage = null; let cardImages = []; const templates = ['やったね!', 'おめでとう!', '祝!', '挑戦', '初', '記念', '仲良し', 'だいすき', 'ありがとう', 'がんばったね']; const frameStyles = { orange: { border: '#f29b38', fill: '#fff8ed', accent: '#ffe1b5' }, simple: { border: '#b9aa98', fill: '#ffffff', accent: '#f5f1ec' }, celebrate: { border: '#f2a33a', fill: '#fff4dc', accent: '#ffd36b' }, friend: { border: '#ee8a7a', fill: '#fff2ef', accent: '#ffd1cc' }, memory: { border: '#76a66f', fill: '#f7fff2', accent: '#d8efcf' } }; const layouts = { medium: { count: 6, cols: 2, rows: 3, cardMmW: 35, cardMmH: 35, pageMmW: 89, pageMmH: 127, pxW: 840, pxH: 1200 }, small: { count: 12, cols: 3, rows: 4, cardMmW: 20, cardMmH: 20, pageMmW: 89, pageMmH: 127, pxW: 840, pxH: 1200 }, wide: { count: 2, cols: 1, rows: 2, cardMmW: 86, cardMmH: 54, pageMmW: 89, pageMmH: 127, pxW: 840, pxH: 1200 }, tall: { count: 2, cols: 2, rows: 1, cardMmW: 54, cardMmH: 86, pageMmW: 127, pageMmH: 89, pxW: 1200, pxH: 840 } }; function roundRect(x, y, w, h, r) { const radius = Math.min(r, w / 2, h / 2); ctx.beginPath(); ctx.moveTo(x + radius, y); ctx.arcTo(x + w, y, x + w, y + h, radius); ctx.arcTo(x + w, y + h, x, y + h, radius); ctx.arcTo(x, y + h, x, y, radius); ctx.arcTo(x, y, x + w, y, radius); ctx.closePath(); } function drawCoverImage(img, x, y, w, h) { const imageRatio = img.width / img.height; const boxRatio = w / h; let sx = 0; let sy = 0; let sw = img.width; let sh = img.height; if (imageRatio > boxRatio) { sw = img.height * boxRatio; sx = (img.width - sw) / 2; } else { sh = img.width / boxRatio; sy = (img.height - sh) / 2; } ctx.imageSmoothingEnabled = true; ctx.imageSmoothingQuality = 'medium'; ctx.drawImage(img, sx, sy, sw, sh, x, y, w, h); } function drawPlaceholder(x, y, w, h) { ctx.fillStyle = '#fff4e1'; ctx.fillRect(x, y, w, h); ctx.fillStyle = '#c98a36'; ctx.font = '700 18px sans-serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('写真', x + w / 2, y + h / 2); } function textInputs() { return Array.from(textList.querySelectorAll('.snpcm-card-caption')); } function dateInputs() { return Array.from(textList.querySelectorAll('.snpcm-card-date')); } function cardValues() { const captions = textInputs(); const dates = dateInputs(); return captions.map(function (input, index) { return { text: input.value, date: dates[index] ? dates[index].value : '' }; }); } function formatDate(value) { if (!value) return ''; const parts = value.split('-').map(function (part) { return Number(part); }); const y = parts[0]; const m = parts[1]; const d = parts[2]; if (dateModeSelect.value === 'dot') return y + '.' + m + '.' + d; if (dateModeSelect.value === 'slash') return y + '/' + m + '/' + d; return y + '年' + m + '月' + d + '日'; } function syncTextFields() { const layout = layouts[layoutSelect.value]; const current = cardValues(); cardImages = cardImages.slice(0, layout.count); textList.innerHTML = ''; for (let i = 0; i < layout.count; i += 1) { const item = document.createElement('div'); item.className = 'snpcm-card-text'; const imageLabel = document.createElement('label'); imageLabel.className = 'snpcm-card-image'; imageLabel.innerHTML = '' + (i + 1) + '枚目の写真'; const imageInput = document.createElement('input'); imageInput.type = 'file'; imageInput.accept = 'image/*'; imageInput.addEventListener('change', function () { loadCardImage(i, imageInput.files && imageInput.files[0]); }); imageLabel.appendChild(imageInput); item.appendChild(imageLabel); const label = document.createElement('label'); label.innerHTML = '' + (i + 1) + '枚目の文字'; const input = document.createElement('input'); input.type = 'text'; input.className = 'snpcm-card-caption'; input.maxLength = '16'; input.value = current[i] ? current[i].text : ''; input.placeholder = '例:やったね!'; input.addEventListener('input', render); label.appendChild(input); item.appendChild(label); const templateRow = document.createElement('div'); templateRow.className = 'snpcm-template-row'; templates.forEach(function (template) { const button = document.createElement('button'); button.type = 'button'; button.className = 'snpcm-template-chip'; button.textContent = template; button.addEventListener('click', function () { input.value = template; render(); }); templateRow.appendChild(button); }); item.appendChild(templateRow); const dateRow = document.createElement('div'); dateRow.className = 'snpcm-card-date-row'; const dateLabel = document.createElement('label'); dateLabel.innerHTML = '日付'; const dateInput = document.createElement('input'); dateInput.type = 'date'; dateInput.className = 'snpcm-card-date'; dateInput.value = current[i] ? current[i].date : ''; dateInput.addEventListener('input', render); dateLabel.appendChild(dateInput); const todayButton = document.createElement('button'); todayButton.type = 'button'; todayButton.textContent = '今日'; todayButton.addEventListener('click', function () { dateInput.value = todayValue(); render(); }); const clearButton = document.createElement('button'); clearButton.type = 'button'; clearButton.textContent = '消す'; clearButton.addEventListener('click', function () { dateInput.value = ''; render(); }); dateRow.appendChild(dateLabel); dateRow.appendChild(todayButton); dateRow.appendChild(clearButton); item.appendChild(dateRow); textList.appendChild(item); } } function drawCard(x, y, w, h, text, dateValue, image) { const style = frameStyles[frameSelect.value] || frameStyles.orange; const radius = radiusSelect.value === 'round' ? Math.max(8, Math.round(Math.min(w, h) * 0.08)) : 3; const captionMode = captionSelect.value; const captionText = (text || '').trim(); const dateText = formatDate(dateValue); const pad = Math.max(6, Math.round(Math.min(w, h) * 0.08)); const labelH = captionMode === 'none' ? 0 : Math.max(20, Math.round(h * 0.2)); const dateH = dateText ? Math.max(12, Math.round(h * 0.08)) : 0; const imageY = captionMode === 'top' ? y + pad + labelH : y + pad; const bottomReserve = (captionMode === 'bottom' ? labelH : 0) + dateH; const imageH = h - pad * 2 - (captionMode === 'top' ? labelH : 0) - bottomReserve; const imageX = x + pad; const imageW = w - pad * 2; const labelY = captionMode === 'top' ? y + pad : y + h - pad - dateH - labelH; ctx.save(); roundRect(x, y, w, h, radius); ctx.fillStyle = style.fill; ctx.fill(); ctx.lineWidth = Math.max(2, Math.round(Math.min(w, h) * 0.035)); ctx.strokeStyle = style.border; ctx.stroke(); ctx.clip(); roundRect(imageX, imageY, imageW, imageH, Math.max(5, radius - 5)); ctx.fillStyle = '#fff'; ctx.fill(); ctx.save(); roundRect(imageX, imageY, imageW, imageH, Math.max(5, radius - 5)); ctx.clip(); if (image) { drawCoverImage(image, imageX, imageY, imageW, imageH); } else { drawPlaceholder(imageX, imageY, imageW, imageH); } ctx.restore(); if (captionMode !== 'none') { roundRect(x + pad, labelY, w - pad * 2, labelH, Math.max(6, radius - 7)); ctx.fillStyle = '#ffffff'; ctx.fill(); ctx.strokeStyle = style.accent; ctx.lineWidth = Math.max(2, Math.round(Math.min(w, h) * 0.018)); ctx.stroke(); ctx.fillStyle = textColorSelect.value; ctx.font = '800 ' + Math.max(12, Math.round(labelH * 0.44)) + 'px sans-serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(captionText || ' ', x + w / 2, labelY + labelH / 2, w - pad * 3); } if (dateText) { ctx.fillStyle = textColorSelect.value; ctx.globalAlpha = 0.82; ctx.font = '700 ' + Math.max(9, Math.round(dateH * 0.72)) + 'px sans-serif'; ctx.textAlign = 'right'; ctx.textBaseline = 'middle'; ctx.fillText(dateText, x + w - pad, y + h - pad - dateH / 2, w - pad * 2); ctx.globalAlpha = 1; } ctx.restore(); } function render() { const layout = layouts[layoutSelect.value]; if (canvas.width !== layout.pxW || canvas.height !== layout.pxH) { canvas.width = layout.pxW; canvas.height = layout.pxH; } const mmToPx = canvas.width / layout.pageMmW; const cardW = layout.cardMmW * mmToPx; const cardH = layout.cardMmH * mmToPx; const totalW = layout.cols * cardW; const totalH = layout.rows * cardH; const gapX = layout.cols > 1 ? (canvas.width - totalW) / (layout.cols + 1) : (canvas.width - totalW) / 2; const gapY = layout.rows > 1 ? (canvas.height - totalH) / (layout.rows + 1) : (canvas.height - totalH) / 2; const cards = cardValues(); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = '#ffffff'; ctx.fillRect(0, 0, canvas.width, canvas.height); for (let i = 0; i < layout.count; i += 1) { const col = i % layout.cols; const row = Math.floor(i / layout.cols); const x = gapX + col * (cardW + gapX); const y = gapY + row * (cardH + gapY); drawCard(x, y, cardW, cardH, cards[i] ? cards[i].text : '', cards[i] ? cards[i].date : '', cardImages[i] || selectedImage); } } function loadImage(file) { if (!file) return; const reader = new FileReader(); reader.onload = function (event) { const img = new Image(); img.onload = function () { selectedImage = img; const layout = layouts[layoutSelect.value]; for (let i = 0; i < layout.count; i += 1) { if (!cardImages[i]) cardImages[i] = img; } render(); }; img.src = event.target.result; }; reader.readAsDataURL(file); } function loadCardImage(index, file) { if (!file) return; const reader = new FileReader(); reader.onload = function (event) { const img = new Image(); img.onload = function () { cardImages[index] = img; render(); }; img.src = event.target.result; }; reader.readAsDataURL(file); } function todayValue() { const now = new Date(); const y = now.getFullYear(); const m = String(now.getMonth() + 1).padStart(2, '0'); const d = String(now.getDate()).padStart(2, '0'); return y + '-' + m + '-' + d; } fileInput.addEventListener('change', function () { loadImage(fileInput.files && fileInput.files[0]); }); layoutSelect.addEventListener('change', function () { syncTextFields(); render(); }); [captionSelect, frameSelect, radiusSelect, textColorSelect, dateModeSelect].forEach(function (item) { item.addEventListener('input', render); item.addEventListener('change', render); }); makeButton.addEventListener('click', render); saveButton.addEventListener('click', function () { render(); const link = document.createElement('a'); link.download = 'uchi-no-ko-photo-card-lban.png'; link.href = canvas.toDataURL('image/png'); link.click(); }); printButton.addEventListener('click', function () { render(); const dataUrl = canvas.toDataURL('image/png'); const win = window.open('', '_blank'); if (!win) return; const img = win.document.createElement('img'); img.src = dataUrl; img.alt = '作成したフォトカード'; img.style.width = '100%'; img.style.height = 'auto'; win.document.body.style.margin = '0'; win.document.body.style.background = '#fff'; win.document.body.appendChild(img); win.focus(); setTimeout(function () { win.print(); }, 500); }); syncTextFields(); render(); const status = root.querySelector('#snpcm-status'); if (status) status.textContent = 'プレビュー表示中です'; } catch (error) { const box = document.getElementById('sn-photo-card-maker'); const message = document.createElement('div'); message.style.cssText = 'margin:12px 0;padding:12px;background:#fff3cd;border:2px solid #f2c98b;border-radius:10px;color:#7b4b18;font-size:13px;line-height:1.7;'; message.textContent = 'フォトカードメーカーの読み込み中にエラーが出ました:' + (error && error.message ? error.message : error); if (box) box.appendChild(message); } })();