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

推荐订阅源

IT之家
IT之家
Last Week in AI
Last Week in AI
博客园_首页
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 叶小钗
大猫的无限游戏
大猫的无限游戏
人人都是产品经理
人人都是产品经理
V
Visual Studio Blog
宝玉的分享
宝玉的分享
博客园 - Franky
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
月光博客
月光博客
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
博客园 - 聂微东
S
SegmentFault 最新的问题
博客园 - 司徒正美
罗磊的独立博客
V
V2EX
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
美团技术团队
小众软件
小众软件
Jina AI
Jina AI

博客园 - 蓓蕾心晴

语雀思维导图如何导入到飞书文档 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. 会用户手势触发的点击,执行复制方法就会报这个错