惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
Vulnerabilities – Threatpost
U
Unit 42
F
Fortinet All Blogs
aimingoo的专栏
aimingoo的专栏
P
Proofpoint News Feed
F
Full Disclosure
月光博客
月光博客
Engineering at Meta
Engineering at Meta
博客园_首页
The Register - Security
The Register - Security
G
Google Developers Blog
The Cloudflare Blog
博客园 - Franky
K
Kaspersky official blog
A
Arctic Wolf
Scott Helme
Scott Helme
C
Cisco Blogs
Hugging Face - Blog
Hugging Face - Blog
C
Check Point Blog
NISL@THU
NISL@THU
AI
AI
D
DataBreaches.Net
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Stack Overflow Blog
Stack Overflow Blog
Project Zero
Project Zero
The GitHub Blog
The GitHub Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
量子位
Vercel News
Vercel News
T
Tor Project blog
P
Privacy International News Feed
D
Docker
I
Intezer
L
LangChain Blog
P
Proofpoint News Feed
Security Latest
Security Latest
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
Threatpost
博客园 - 聂微东
AWS News Blog
AWS News Blog
Martin Fowler
Martin Fowler
P
Privacy & Cybersecurity Law Blog
V
V2EX
Last Week in AI
Last Week in AI
C
Cybersecurity and Infrastructure Security Agency CISA
The Hacker News
The Hacker News
T
Tenable Blog
Blog — PlanetScale
Blog — PlanetScale
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Tailwind CSS Blog

博客园 - 蓓蕾心晴

语雀思维导图如何导入到飞书文档 css 背景模糊在真机测试会出现黑色蒙层闪现问题解决 华为鸿蒙手机通过Chrome DevTools调试App内WebView页面 vscode左侧搜索栏搜索时排除不参与搜索的文件夹 css动画已经执行过一次如何再次执行? vscode设置单击选中带连字符的单词 移动端盒子元素实现左右可滑动且竖向页面可滑动 js 判断设备类型包括异形屏 element ui 日期组件实现仅显示日期选择但值包含固定的时间 master远端代码更新,本地拉取不到 css 实现刘海屏样式兼容并支持 js 获取刘海屏高度后动态修改 css 判断在支持某些属性的情况下再添加样式 vue3 provide的值 在回调函数中改变,inject 如何获取到最新的值? vue3如何将 app 全局变量对象变为响应式并监听到某个属性的改变 ResizeObserver loop completed with undelivered notifications. 报错 git 修改本地仓库的远程仓库地址 git突然无法推送到远程仓库 css实现图片等比例完全展示,背景加图片 200%放大虚化 element-ui 使用 el-date-picker 如何限制时间选择范围? element-ui 使用 el-date-picker 如何监听数据变更?
js 实现点击触发复制口令到剪贴板,并跳转
蓓蕾心晴 · 2026-03-31 · via 博客园 - 蓓蕾心晴

vue3 项目,想要使用 js 实现点击触发跳转时,复制口令到剪贴板,注意这里,仅仅是复制到剪贴板。

注意事项:

  • 复制到剪贴板方法必须在用户主动触发手势,click、touchstart、 touchend、keybord 等
  • 触发复制的方法必须在点击后非异步的方法内执行,即必须在用户手势(如 clicktouchstart)事件的同步代码中调用,如果在 setTimeout、promise、事件监听等 内或者后面都会导致执行失败
  • 摇一摇等非用户手势触发的点击这些都属于自动点击,则不会复制成功,会报错:Document is not focused.
/**
 * 复制文本到剪贴板
 * 注意:必须在用户手势(如 click、touchstart)的同步调用链中执行,不能在异步回调中调用!
 * @param {string} text - 要复制的文本
 * @returns {Promise<boolean>} - 是否复制成功
 */
export function copyToClipboard(text) {
  return new Promise((resolve) => {
    // 优先使用现代 Clipboard API
    if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
      navigator.clipboard.writeText(text)
        .then(() => {
          console.log('[copyToClipboard] Clipboard API 复制成功')
          resolve(true)
        })
        .catch((err) => {
          console.warn('[copyToClipboard] Clipboard API 失败:', err.message)
          const success = fallbackCopy(text)
          console.log('[copyToClipboard] 降级方案结果:', success)
          resolve(success)
        })
    } else {
      console.log('[copyToClipboard] Clipboard API 不可用,使用降级方案')
      const success = fallbackCopy(text)
      console.log('[copyToClipboard] 降级方案结果:', success)
      resolve(success)
    }
  })
}

/**
 * 降级复制方案(使用 textarea + execCommand,增强 iOS 兼容性)
 * @param {string} text - 要复制的文本
 * @returns {boolean} - 是否复制成功
 */
function fallbackCopy(text) {
  const textarea = document.createElement('textarea')
  textarea.value = text
  // 关键:设置 contentEditable 为 true,提高 iOS 选中兼容性
  textarea.contentEditable = 'true'
  textarea.style.cssText = `
    position: fixed;
    top: 0;
    left: 0;
    opacity: 0;
    pointer-events: none;
    width: 100px;
    height: 100px;
    z-index: -1;
  `
  document.body.appendChild(textarea)

  // iOS 兼容性处理
  const isIOS = /iPhone|iPad|iPod/i.test(navigator.userAgent)
  let success = false

  try {
    // 聚焦元素(iOS 需要)
    textarea.focus()

    if (isIOS) {
      const range = document.createRange()
      range.selectNodeContents(textarea)
      const selection = window.getSelection()
      selection.removeAllRanges()
      selection.addRange(range)
      textarea.setSelectionRange(0, text.length)
    } else {
      textarea.select()
      textarea.setSelectionRange(0, text.length)
    }

    success = document.execCommand('copy')
    console.log('[fallbackCopy] execCommand 复制结果:', success)
  } catch (err) {
    console.error('[fallbackCopy] execCommand 异常:', err)
    success = false
  } finally {
    document.body.removeChild(textarea)
  }
  return success
}

/**
 * 通过 scheme 打开 APP
 * @param {string} scheme - 完整的 scheme URL
 * @returns {void}
 */
export function openAppByScheme(scheme) {
  const isIOS = /iPhone|iPad|iPod/i.test(navigator.userAgent)

  if (isIOS) {
    // iOS 使用 iframe 方式,避免页面跳转
    const iframe = document.createElement('iframe')
    iframe.style.display = 'none'
    iframe.src = scheme
    document.body.appendChild(iframe)
    setTimeout(() => {
      document.body.removeChild(iframe)
    }, 100)
  } else {
    // Android 直接跳转
    window.location.href = scheme
  }
}

/**
 * 复制口令并打开 APP
 * @param {string} jumpCommand - 口令内容
 * @param {string} schemePrefix - scheme 前缀,默认为抖音极速版
 * @param {object} options - 配置选项
 * @param {number} options.delay - 复制后延迟打开的时间(ms),默认 300ms
 * @returns {Promise<void>}
 */
export async function copyAndOpenApp(jumpCommand, schemePrefix = DEFAULT_SCHEME_PREFIX, options = {}) {
  const { delay = 300 } = options

  if (!jumpCommand) {
    console.warn('[copyAndOpenApp] jumpCommand 为空,跳过复制')
    return
  }

  // 复制口令
  const copied = await copyToClipboard(jumpCommand)

  if (!copied) {
    console.warn('[copyAndOpenApp] 复制失败,但仍尝试打开 APP')
  }

  // 拼接 scheme 并打开 APP
  const scheme = `${schemePrefix}search?keyword=${encodeURIComponent(jumpCommand)}`

  // 延迟打开,确保复制操作完成
  setTimeout(() => {
    openAppByScheme(scheme)
  }, delay)
}

/**
 * 复制口令到剪贴板
 * @param {string} jumpCommand - 口令内容
 */

export async function copyCommand(jumpCommand) {
  if (!jumpCommand) {
    console.warn('[copyCommand] jumpCommand 为空,跳过复制')
    return
  }

  // 复制口令
  const copied = await copyToClipboard(jumpCommand)

  if (!copied) {
    console.warn('[copyCommand] 复制失败,但仍尝试打开 APP')
  }
}

以上方法,如果仅复制口令,则使用  copyCommand,如果还要实现复制口令并打开,则使用 copyAndOpenApp。

常见报错:

  1. NotAllowedError: Failed to execute 'writeText' on 'Clipboard': Write permission denied.在异步函数之后或之内触发复制方法就会报这个错
  2. Document is not focused. 会用户手势触发的点击,执行复制方法就会报这个错