如何在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(/ /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>
关键说明
- 正则优先级:先匹配YouTube URL,避免被普通URL正则覆盖
- 光标处理:通过保存/恢复光标位置,保证输入体验流畅
- 格式兼容:将HTML标签转换为纯文本匹配URL,完成后再还原格式
- YouTube权限:iframe添加了必要的播放权限属性,确保视频正常播放
内容的提问来源于stack exchange,提问作者redoc01
相关产品推荐
相关产品推荐

