网站免费推广方案,咨询公司前景好不好,吾爱源码,平面设计能干一辈子吗一 功能#xff0c;记录日记键盘也是浏览器自带二 已解决问题1 . 每次点删除#xff08;⌫#xff09;之后#xff0c;键盘收起/光标丢失#xff0c;得再点一下输入框才能继续#xff1b;2. 插入字母后光标总是跑到最前面#xff0c;看起来像“倒着输入”。根源其实就一…一 功能记录日记键盘也是浏览器自带二 已解决问题1 . 每次点删除⌫之后键盘收起/光标丢失得再点一下输入框才能继续2. 插入字母后光标总是跑到最前面看起来像“倒着输入”。根源其实就一句话。 我们自己改了 value但浏览器并不知道光标该停在哪于是它干脆 reset 到 0。只要把“光标位置”这件事接管过来两个症状会同时消失。!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0, maximum-scale1.0, user-scalableno, viewport-fitcover title隐私空间Pro/title style :root { --primary: #6C5CE7; --bg-grad: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 50%, #90caf9 100%); --glass: rgba(255, 255, 255, 0.25); --glass-border: rgba(255, 255, 255, 0.4); --text-color: #fff; --item-bg: rgba(255,255,255, 0.9); --dark-text: #333; --kb-bg: #d1d5db; --kb-key-bg: #fff; } * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; } body, html { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; font-family: -apple-system, BlinkMacSystemFont, SF Pro SC, PingFang SC, Microsoft YaHei, sans-serif; background: var(--bg-grad); color: var(--text-color); } /* 页面基础*/ .page { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; flex-direction: column; transition: transform 0.4s cubic-bezier(0.25, 0.8, 0.25, 1), opacity 0.4s; opacity: 0; pointer-events: none; transform: scale(0.95); z-index: 1; padding-top: env(safe-area-inset-top); padding-bottom: env(safe-area-inset-bottom); } .page.active { opacity: 1; pointer-events: auto; transform: scale(1); z-index: 10; } /* ----- 锁定页面----- */ .lock-container { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; backdrop-filter: blur(10px); } .lock-title { font-size: 24px; margin-bottom: 30px; font-weight: 600; text-shadow: 0 2px 4px rgba(0,0,0,0.2); } .pin-display { display: flex; gap: 15px; margin-bottom: 50px; } .pin-dot { width: 20px; height: 20px; border: 2px solid #fff; border-radius: 50%; transition: all 0.2s; } .pin-dot.filled { background: #fff; box-shadow: 0 0 10px rgba(255,255,255,0.8); } .numpad { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; width: 80%; max-width: 300px; } .num-btn { background: var(--glass); border: 1px solid var(--glass-border); color: #fff; font-size: 24px; height: 70px; width: 70px; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto; cursor: pointer; } .num-btn:active { background: rgba(255,255,255,0.4); transform: scale(0.95); } /* ----- 列表页面----- */ .header { padding: 15px 20px; display: flex; justify-content: space-between; align-items: center; background: rgba(0,0,0,0.1); backdrop-filter: blur(5px); } .header-title { font-size: 18px; font-weight: bold; } .icon-btn { background: none; border: none; color: #fff; font-size: 24px; cursor: pointer; padding: 5px; opacity: 0.9; } .diary-grid { flex: 1; overflow-y: auto; padding: 15px; display: grid; grid-template-columns: repeat(4, 1fr); grid-auto-rows: min-content; gap: 10px; align-content: start; } .diary-item { aspect-ratio: 1; background: var(--item-bg); border-radius: 12px; display: flex; flex-direction: column; align-items: center; justify-content: center; color: var(--dark-text); box-shadow: 0 4px 6px rgba(0,0,0,0.1); animation: popIn 0.3s ease forwards; overflow: hidden; position: relative; } .diary-item-date { font-weight: bold; font-size: 14px; margin-bottom: 4px; } .diary-item-preview { opacity: 0.6; font-size: 10px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; width: 80%; text-align: center; } .fab { position: absolute; bottom: 30px; right: 30px; width: 60px; height: 60px; background: #fff; color: var(--primary); border-radius: 50%; border: none; font-size: 30px; box-shadow: 0 5px 20px rgba(0,0,0,0.3); display: flex; align-items: center; justify-content: center; z-index: 100; cursor: pointer; } /* ----- 编辑页面----- */ .editor-container { flex: 1; display: flex; flex-direction: column; background: #f5f7fa; color: #333; position: relative; height: 100%; } .editor-nav { padding: 15px; display: flex; justify-content: space-between; align-items: center; background: #fff; box-shadow: 0 2px 5px rgba(0,0,0,0.05); flex-shrink: 0; } .nav-btn { color: var(--primary); font-weight: 600; font-size: 16px; cursor: pointer; } .editor-date { font-size: 16px; font-weight: bold; } .editor-textarea { flex: 1; width: 100%; border: none; padding: 20px; font-size: 18px; line-height: 1.5; resize: none; outline: none; background: transparent; font-family: inherit; } /* ----- 隐私键盘(English Only) ----- */ .privacy-keyboard-container { position: absolute; bottom: 0; left: 0; width: 100%; background: #d1d5db; transform: translateY(100%); transition: transform 0.3s cubic-bezier(0.1, 0.7, 0.1, 1); z-index: 500; display: flex; flex-direction: column; padding-bottom: env(safe-area-inset-bottom); } .privacy-keyboard-container.show { transform: translateY(0); } .keyboard-security-tip { background: #fef3c7; color: #d97706; font-size: 12px; text-align: center; padding: 6px; border-top: 1px solid #fcd34d; display: flex; align-items: center; justify-content: center; gap: 5px; } .pk-row { display: flex; justify-content: center; width: 100%; padding: 6px 3px 0 3px; gap: 6px; } .pk-key { background: #fff; border-radius: 5px; height: 44px; flex: 1; display: flex; align-items: center; justify-content: center; font-size: 20px; font-weight: 400; color: #000; box-shadow: 0 1px 0 rgba(0,0,0,0.3); cursor: pointer; user-select: none; max-width: 40px; /* 限制字母键宽度*/ } .pk-key:active { background: #e5e7eb; transform: translateY(1px); box-shadow: none; } .pk-key.wide { flex: 1.5; font-size: 14px; background: #aeb4be; max-width: none; } .pk-key.space { flex: 5; max-width: none; } .pk-key.enter { background: var(--primary); color: white; font-size: 14px; font-weight: bold; max-width: none; flex: 2; } .spacer { height: 6px; } /* ----- 设置模态框----- */ .modal-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 900; display: none; align-items: center; justify-content: center; backdrop-filter: blur(3px); } .modal-content { background: white; padding: 25px; border-radius: 16px; width: 80%; max-width: 320px; color: #333; box-shadow: 0 10px 30px rgba(0,0,0,0.3); animation: popIn 0.2s; } .modal-header { font-size: 18px; font-weight: bold; margin-bottom: 20px; } .setting-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; font-size: 16px; } /* 简单的Toggle 开关*/ .toggle-switch { position: relative; width: 50px; height: 26px; border-radius: 13px; background: #ccc; cursor: pointer; transition: 0.3s; } .toggle-switch.on { background: var(--primary); } .toggle-handle { position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: 50%; background: white; transition: 0.3s; } .toggle-switch.on .toggle-handle { left: 27px; } .modal-footer { text-align: right; margin-top: 10px; } .modal-btn { padding: 8px 16px; background: #eee; border: none; border-radius: 6px; font-size: 14px; } /* 动画与工具*/ keyframes shake { 0%,100%{transform:translateX(0)} 25%{transform:translateX(-8px)} 75%{transform:translateX(8px)} } .shake { animation: shake 0.3s ease-in-out; } keyframes popIn { from {opacity:0; transform:scale(0.8)} to{opacity:1; transform:scale(1)} } #toast { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0,0,0,0.8); color: white; padding: 10px 20px; border-radius: 8px; opacity: 0; pointer-events: none; transition: opacity 0.3s; z-index: 1000; font-size: 14px; } /style /head body !-- 1. 登录/设置页面-- div idpage-lock classpage active div classlock-container div classlock-title idlock-msg请输入密码/div div classpin-display idpin-dots div classpin-dot/div div classpin-dot/div div classpin-dot/div div classpin-dot/div /div div classnumpad div classnum-btn onclickinputPin(1)1/div div classnum-btn onclickinputPin(2)2/div div classnum-btn onclickinputPin(3)3/div div classnum-btn onclickinputPin(4)4/div div classnum-btn onclickinputPin(5)5/div div classnum-btn onclickinputPin(6)6/div div classnum-btn onclickinputPin(7)7/div div classnum-btn onclickinputPin(8)8/div div classnum-btn onclickinputPin(9)9/div div classnum-btn styleopacity:0/div div classnum-btn onclickinputPin(0)0/div div classnum-btn onclickdeletePin()⌫/div /div /div /div !-- 2. 日记列表页面-- div idpage-list classpage div classheader span classheader-titlePrivacy Diary/span div styledisplay: flex; gap: 15px; button classicon-btn onclickopenSettings()⚙️/button /div /div div classdiary-grid iddiary-grid/div button classfab onclickopenEditor(null)/button /div !-- 3. 编辑页面-- div idpage-editor classpage stylebackground: white; div classeditor-container div classeditor-nav div classnav-btn onclickcloseEditor()取消/div div classeditor-date idedit-date-display.../div div classnav-btn onclicksaveEditor()完成/div /div !-- 核心输入框inputmodenone 阻止手机键盘弹出-- textarea iddiary-input classeditor-textarea placeholder在此输入内容... inputmodetext/textarea !-- 模拟光标填充视图防止键盘遮挡-- div idkb-spacer styleheight: 0; transition: height 0.3s;/div !-- 隐私键盘-- div idprivacy-keyboard classprivacy-keyboard-container div classkeyboard-security-tip span stylefont-weight:bold;隐私键盘已开启/span- 输入内容绝不联网本地安全存储 /div div classpk-row div classpk-key onclickk(Q)Q/div div classpk-key onclickk(W)W/div div classpk-key onclickk(E)E/div div classpk-key onclickk(R)R/div div classpk-key onclickk(T)T/div div classpk-key onclickk(Y)Y/div div classpk-key onclickk(U)U/div div classpk-key onclickk(I)I/div div classpk-key onclickk(O)O/div div classpk-key onclickk(P)P/div /div div classpk-row div classpk-key onclickk(A)A/div div classpk-key onclickk(S)S/div div classpk-key onclickk(D)D/div div classpk-key onclickk(F)F/div div classpk-key onclickk(G)G/div div classpk-key onclickk(H)H/div div classpk-key onclickk(J)J/div div classpk-key onclickk(K)K/div div classpk-key onclickk(L)L/div /div div classpk-row div classpk-key wide onclickk(CAPS)⬆/div div classpk-key onclickk(Z)Z/div div classpk-key onclickk(X)X/div div classpk-key onclickk(C)C/div div classpk-key onclickk(V)V/div div classpk-key onclickk(B)B/div div classpk-key onclickk(N)N/div div classpk-key onclickk(M)M/div div classpk-key wide onclickk(DEL)⌫/div /div div classpk-row stylemargin-bottom: 5px; div classpk-key wide onclickk(123)123/div div classpk-key onclickk(,),/div div classpk-key space onclickk(SPACE)Space/div div classpk-key onclickk(.)./div div classpk-key enter onclickk(ENTER)Enter/div /div div classspacer/div /div /div /div !-- 设置模态框-- div idsettings-modal classmodal-overlay div classmodal-content div classmodal-header设置/div div classsetting-row span️开启隐私键盘/span div classtoggle-switch on idtoggle-privacy onclicktogglePrivacy() div classtoggle-handle/div /div /div div stylefont-size: 12px; color: #666; margin-bottom: 20px; line-height: 1.4; 开启后写日记时将使用内置安全键盘仅支持英文 彻底防止第三方输入法窃取记录。 /div div classsetting-row styleborder-top: 1px solid #eee; padding-top: 15px; span stylecolor: red; onclicklockApp() 立即锁定App/span /div div classmodal-footer button classmodal-btn onclickcloseSettings() 关闭/button /div /div /div div idtoast提示信息/div script // --- 全局变量--- const DB_KEY xs_diary_data_v2; const PIN_KEY xs_diary_pin; const SETTING_KEY xs_diary_privacy_on; let diaryData []; let currentPin ; let isSetupMode false; let setupStep 1; let tempPin ; let currentEditingId null; // 隐私键盘状态 let isPrivacyOn true; let isCaps false; // --- 工具函数--- // 把光标强制放到字符串末尾新建日记时很好用 function moveCursorToEnd(el) { el.focus(); const len el.value.length; el.setSelectionRange(len, len); el.scrollTop el.scrollHeight; // 如果内容超高顺便滚到底 } // 在任意位置插入文本并自动把光标放到插入片段之后 function insertAtCaret(el, str) { const [start, end] [el.selectionStart, el.selectionEnd]; const before el.value.substring(0, start); const after el.value.substring(end); el.value before str after; // 关键把光标放到新插入文本的后面 const newPos start str.length; el.setSelectionRange(newPos, newPos); // 让 textarea 重新获得焦点防止键盘丢失 el.focus(); el.scrollTop el.scrollHeight; } // --- 初始化--- window.addEventListener(DOMContentLoaded, () { loadData(); loadSettings(); checkLockStatus(); // 绑定输入框聚焦事件 const input document.getElementById(diary-input); input.addEventListener(focus, onInputFocus); // 如果用户自己点了系统键盘我们就乖乖把隐私键盘收起来 input.addEventListener(blur, (e) { // 如果是因为切到别的输入框而 blur就不处理 if (e.relatedTarget null) return; document.getElementById(privacy-keyboard).classList.remove(show); }); }); function loadData() { const data localStorage.getItem(DB_KEY); diaryData data ? JSON.parse(data) : []; } function loadSettings() { const val localStorage.getItem(SETTING_KEY); // 默认开启 if (val null) { isPrivacyOn true; localStorage.setItem(SETTING_KEY, true); } else { isPrivacyOn val true; } updateToggleUI(); } // --- 键盘与输入逻辑--- // 当点击输入框时 function onInputFocus(e) { const input document.getElementById(diary-input); const kbContainer document.getElementById(privacy-keyboard); const spacer document.getElementById(kb-spacer); if (isPrivacyOn) { // 如果开启隐私键盘禁止系统键盘弹出 input.setAttribute(inputmode, none); kbContainer.classList.add(show); spacer.style.height kbContainer.offsetHeight px; // 垫高底部防止遮挡 } else { // 如果关闭隐私键盘允许系统键盘 input.setAttribute(inputmode, text); kbContainer.classList.remove(show); spacer.style.height 0; } } // 虚拟按键处理 function k(key) { const input document.getElementById(diary-input); let val input.value; let start input.selectionStart; let end input.selectionEnd; let charToAdd ; if (key DEL) { if (start end) { if (start 0) { input.value val.substring(0, start - 1) val.substring(end); input.setSelectionRange(start - 1, start - 1); } } else { input.value val.substring(0, start) val.substring(end); input.setSelectionRange(start, start); } input.focus(); // ← 就加这一行 return; // 结束 } else if (key SPACE) { charToAdd ; } else if (key ENTER) { charToAdd \n; } else if (key CAPS) { isCaps !isCaps; // document.querySelectorAll(.pk-key).forEach(...) // 这里可以做UI变色为了简化暂略 return; } else if (key 123) { // 简化版这里暂不支持切换数字键盘直接插入示例数字以免代码过长或者作为TODO insertTextAtCursor(1); return; } else { // 字母 charToAdd isCaps ? key : key.toLowerCase(); } insertTextAtCursor(charToAdd); } // 原来那段直接删掉换成这一行即可 function insertTextAtCursor(text) { const input document.getElementById(diary-input); insertAtCaret(input, text); } // --- 设置面板逻辑--- function openSettings() { document.getElementById(settings-modal).style.display flex; } function closeSettings() { document.getElementById(settings-modal).style.display none; } function togglePrivacy() { isPrivacyOn !isPrivacyOn; localStorage.setItem(SETTING_KEY, isPrivacyOn); updateToggleUI(); } function updateToggleUI() { const toggle document.getElementById(toggle-privacy); if (isPrivacyOn) toggle.classList.add(on); else toggle.classList.remove(on); } // --- 核心业务逻辑(与上个版本类似但增加了保存) --- function checkLockStatus() { const storedPin localStorage.getItem(PIN_KEY); if (!storedPin) { isSetupMode true; document.getElementById(lock-msg).innerText 首次使用 请设置4位密码; } else { isSetupMode false; document.getElementById(lock-msg).innerText 请输入密码解锁; } } function inputPin(num) { if (currentPin.length 4) { currentPin num; updateDots(); if (currentPin.length 4) setTimeout(processPin, 200); } } function deletePin() { currentPin currentPin.slice(0, -1); updateDots(); } function updateDots() { const dots document.querySelectorAll(.pin-dot); dots.forEach((dot, idx) { if (idx currentPin.length) dot.classList.add(filled); else dot.classList.remove(filled); }); } function processPin() { const storedPin localStorage.getItem(PIN_KEY); if (isSetupMode) { if (setupStep 1) { tempPin currentPin; currentPin ; setupStep 2; document.getElementById(lock-msg).innerText 请再次输入以确认; updateDots(); } else { if (currentPin tempPin) { localStorage.setItem(PIN_KEY, currentPin); showToast(密码设置成功); unlockApp(); } else { showToast(密码不一致重试); shakeLock(); resetSetup(); } } } else { if (currentPin storedPin) unlockApp(); else { showToast( 密码错误); shakeLock(); currentPin ; updateDots(); } } } function resetSetup() { currentPin ; tempPin ; setupStep 1; document.getElementById(lock-msg).innerText 首次使用 请设置密码; updateDots(); } function shakeLock() { const el document.querySelector(.lock-container); el.classList.add(shake); setTimeout(() el.classList.remove(shake), 300); } function switchPage(pageId) { document.querySelectorAll(.page).forEach(p p.classList.remove(active)); document.getElementById(pageId).classList.add(active); } function unlockApp() { currentPin ; updateDots(); renderGrid(); switchPage(page-list); } function lockApp() { closeSettings(); switchPage(page-lock); // 关闭键盘 document.getElementById(privacy-keyboard).classList.remove(show); } function renderGrid() { const grid document.getElementById(diary-grid); grid.innerHTML ; const sorted [...diaryData].sort((a,b) b.ts - a.ts); if (sorted.length 0) { grid.innerHTML div stylegrid-column:1/-1; text-align:center; opacity:0.6; margin-top:50px; 点击右下角 开始写日记 /div; return; } sorted.forEach(item { const el document.createElement(div); el.className diary-item; const d new Date(item.ts); el.innerHTML div classdiary-item-date${d.getMonth()1}/${d.getDate()}/divdiv classdiary-item-preview${item.content || 空}/div; el.onclick () openEditor(item.id); grid.appendChild(el); }); } function openEditor(id) { currentEditingId id; const input document.getElementById(diary-input); const dateDisplay document.getElementById(edit-date-display); if (id) { const item diaryData.find(x x.id id); input.value item ? item.content : ; dateDisplay.innerText new Date(item.ts).toLocaleDateString(); } else { input.value ; dateDisplay.innerText new Date().toLocaleDateString(); } switchPage(page-editor); // 延迟聚焦如果是隐私模式会自动弹出自定义键盘 setTimeout(() { const input document.getElementById(diary-input); moveCursorToEnd(input); // ← 新增 if (isPrivacyOn) { document.getElementById(privacy-keyboard).classList.add(show); } }, 300); } function closeEditor() { document.getElementById(diary-input).blur(); document.getElementById(privacy-keyboard).classList.remove(show); document.getElementById(kb-spacer).style.height 0; switchPage(page-list); } function saveEditor() { const content document.getElementById(diary-input).value.trim(); if(!content) { closeEditor(); return; } const now Date.now(); if (currentEditingId) { const idx diaryData.findIndex(x x.id currentEditingId); if (idx -1) { diaryData[idx].content content; diaryData[idx].ts now; } } else { diaryData.push({ id: d_now, content: content, ts: now }); } localStorage.setItem(DB_KEY, JSON.stringify(diaryData)); renderGrid(); closeEditor(); showToast(已加密保存); } function showToast(msg) { const t document.getElementById(toast); t.innerText msg; t.style.opacity 1; setTimeout(() t.style.opacity 0, 2000); } /script /body /html