You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在WebView中用JS将普通URL转锚点、YouTube URL转iframe

问题:将WebView可编辑内容中的URL转换为锚点或YouTube iframe

之前尝试使用react-native-pell-rich-editor未满足需求,现改用React Native的WebView实现了输入框占位符功能。当前需要编写JavaScript代码,将#contentHtml中的普通URL替换为<a>锚点标签,将各类YouTube URL替换为<iframe>标签,需兼容YouTube的多种URL变体。

示例输入

str = `
        https://www.google.com
        http://google.com
        https://www.youtube.com/live/gNIQWYgf-0
        https://www.youtube.com/embed/3ul2LYG6j14?si=fgxYHjyt6zBmoYEr
        https://youtu.be/75Dhfjf6hfjfj
        this also has to take into account the variations of different youtube urls
  `

期望转换结果

普通URL转为锚点

<a href="https://www.google.com">https://www.google.com</a>
<a href="http://google.com">http://google.com</a>

YouTube URL转为iframe

<iframe width="420" height="315" src="https://youtu.be/75Dhfjf6hfjfj"></iframe>
<iframe width="420" height="315" src="https://www.youtube.com/embed/3ul2LYG6j14?si=fgxYHjyt6zBmoYEr"></iframe>

现有WebView代码

<WebView source={{
            html: `
                    <!DOCTYPE html>
                    <html>
                        <head>
                            <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
                            
                        </head>
                        <body>
                        <style>
                                [contentEditable=true]:empty:not(:focus):before {
                                    content: attr(data-text);
                                    cursor: text;
                                    color: red;
                                    pointer-events:none;
                                }

                                #placeholder{
                                    position:absolute;
                                    
                                    color:#BDBDBD;font-size:46px;outline:none;
                                }
                                #contentHtml{
                                    position:absolute;
                                    color:black;font-size:46px;outline:none;width:100%;
                                }
                            </style>
                            <div id="contentHtml" contenteditable=true>
                            </div>
                            <div id="placeholder">
                                Enter Comment...
                            </div>
                        </body>
                    </html>
                    <script>
                        $("#placeholder").on("click", function(){
                            $("#contentHtml").focus();
                            $(this).hide();
                        })

                        $("#contentHtml").on("keyup", function(){
                            if($("#contentHtml").html().length == 0){
                                $("#placeholder").show();
                            }else{
                                $("#placeholder").hide();
                            }
                        })
                    </script>
                    
                    `,
        }}
        style={{
            width: '100%',
            height: '100%',
            fontSize:46,
        }}>
    </WebView>

解决方案

通过正则匹配不同类型的URL并替换为对应HTML元素,同时处理contenteditable的光标位置问题,避免替换后光标跳转。修改WebView内嵌的<script>部分即可实现:

<script>
// 处理URL转换的核心函数
function convertUrlsToElements(text) {
  // 优先匹配YouTube各类URL(live/embed/watch/youtu.be)
  const youtubePattern = /https?:\/\/(www\.)?(youtube\.com\/(live|embed|watch\?v=)|youtu\.be\/)[^\s]+/gi;
  text = text.replace(youtubePattern, (url) => {
    return `<iframe width="420" height="315" src="${url}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>`;
  });

  // 匹配普通HTTP/HTTPS URL(排除已处理的YouTube链接)
  const urlPattern = /https?:\/\/[^\s]+/gi;
  text = text.replace(urlPattern, (url) => {
    return `<a href="${url}" target="_blank">${url}</a>`;
  });

  return text;
}

// 保存当前光标位置,避免替换后光标跳转
function saveSelection() {
  const sel = window.getSelection();
  if (sel.rangeCount === 0) return null;
  const range = sel.getRangeAt(0);
  const preSelectionRange = range.cloneRange();
  preSelectionRange.selectNodeContents(document.getElementById('contentHtml'));
  preSelectionRange.setEnd(range.startContainer, range.startOffset);
  const start = preSelectionRange.toString().length;

  return {
    start: start,
    end: start + range.toString().length
  };
}

// 恢复光标位置
function restoreSelection(savedSel) {
  const el = document.getElementById('contentHtml');
  let charIndex = 0;
  const range = document.createRange();
  range.setStart(el, 0);
  range.collapse(true);
  const nodeStack = [el];
  let node, foundStart = false, stop = false;

  while (!stop && (node = nodeStack.pop())) {
    if (node.nodeType === Node.TEXT_NODE) {
      const nextCharIndex = charIndex + node.length;
      if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
        range.setStart(node, savedSel.start - charIndex);
        foundStart = true;
      }
      if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
        range.setEnd(node, savedSel.end - charIndex);
        stop = true;
      }
      charIndex = nextCharIndex;
    } else {
      let i = node.childNodes.length;
      while (i--) {
        nodeStack.push(node.childNodes[i]);
      }
    }
  }

  const sel = window.getSelection();
  sel.removeAllRanges();
  sel.addRange(range);
}

// 占位符点击事件
$("#placeholder").on("click", function(){
  $("#contentHtml").focus();
  $(this).hide();
})

// 监听输入事件(比keyup更全面),处理URL转换和占位符显示
$("#contentHtml").on("input", function(){
  const $el = $(this);
  const savedSelection = saveSelection();
  
  // 处理HTML格式,转换为纯文本便于匹配URL
  const originalHtml = $el.html();
  const text = originalHtml.replace(/<br>/g, '\n')
                           .replace(/<\/?div>/g, '\n')
                           .replace(/&nbsp;/g, ' ');
  
  // 执行URL转换
  const convertedText = convertUrlsToElements(text);
  
  // 将转换后的文本还原为HTML格式,保持换行
  const convertedHtml = convertedText.replace(/\n/g, '<br>');
  
  // 更新内容并恢复光标位置
  $el.html(convertedHtml);
  if (savedSelection) {
    setTimeout(() => restoreSelection(savedSelection), 0);
  }

  // 占位符显示/隐藏逻辑
  if($("#contentHtml").html().trim().length === 0){
    $("#placeholder").show();
  }else{
    $("#placeholder").hide();
  }
})
</script>

关键说明

  1. 正则优先级:先匹配YouTube URL,避免被普通URL正则覆盖
  2. 光标处理:通过保存/恢复光标位置,保证输入体验流畅
  3. 格式兼容:将HTML标签转换为纯文本匹配URL,完成后再还原格式
  4. YouTube权限:iframe添加了必要的播放权限属性,确保视频正常播放

内容的提问来源于stack exchange,提问作者redoc01

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 01:50:58