(function(){ const SourceList = { 'snap':{ url:'//n.sinaimg.cn/finance/fe/snapdom.js', id:'sfa__snapDom' }, 'qr':{ url:'//n.sinaimg.cn/finance/fe/awesome-qr.js', id:'sfa__awesome-qr' }, 'jsbridge':{ url:'//finance.sina.com.cn/other/src/app/SFJSBridge.js', id:'sfa__SFJSBridge' } } //toDo,判断css是否存在,判断dom是否存在,判断snapDom是否存在,判断 function appendCss(){ const CSSId = 'sfa__share__qrbottom__css'; if(document.getElementById(CSSId)==null){ // 创建一个style元素 var styleElement = document.createElement('style'); styleElement.id = CSSId // 定义CSS字符串 var cssString = ` .sfa__share__qrbottom__wrap{width: 750px;background-color: #fff;position:fixed;top:9999em;left:0;} .sfa__share__qrbottom__dark{background-color: #1A1B1D;} .sfa__share__qrbottom__wrap .sfa__target__img{width:100%;} .sfa__share__qrbottom__wrap .sfa__target__img img{width: 100%;display: block;} .sfa__share__qrbottom__wrap .sfa__bottom__part{display: flex;justify-content: space-between;align-items: center;width: 750px;padding: 0 40px;height: 230px;background: url(//n.sinaimg.cn/finance/2024swhjbj/img/share_pic01.56962ec.png) no-repeat bottom;background-size: 750px 176px;box-sizing:border-box;} .sfa__share__qrbottom__wrap .sfa__logo__img{width: 350px;height: 76px;margin-top: 20px;} .sfa__share__qrbottom__wrap .sfa__logo__img img{width: 100%;display: block;} .sfa__share__qrbottom__wrap .sfa__search__bar{background: url(//n.sinaimg.cn/finance/cece9e13/20251202/searchbar.png) 0 0 no-repeat;background-size: 481px auto;width: 481px;height: 54px;box-sizing: border-box;color: #386DC2;font-weight: bold;padding-left: 299px;line-height:54px;} .sfa__share__qrbottom__wrap .sfa__search__bar{font-size: 20px;} .sfa__share__qrbottom__wrap .sfa_share_search_text4{} .sfa__share__qrbottom__wrap .sfa_share_search_text6{font-size:16px;padding-left:295px;} .sfa__share__qrbottom__wrap .sfa_share_search_text8{font-size:14px;padding-left:286px;} .sfa__share__qrbottom__dark.sfa__share__qrbottom__wrap .sfa__search__bar{background: url(//n.sinaimg.cn/finance/cece9e13/20251202/searchbar_dark.png) 0 0 no-repeat;background-size: 481px auto;} .sfa__share__qrbottom__wrap .sfa__share_qr {width: 146px;height: 146px;} .sfa__share__qrbottom__wrap .sfa__share_qr img{width: 100%;} .sfa__share__qrbottom__wrap .sfa__logo__light{display: block;} .sfa__share__qrbottom__wrap .sfa__logo__dark{display: none;} .sfa__share__qrbottom__dark.sfa__share__qrbottom__wrap .sfa__logo__light{display: none;} .sfa__share__qrbottom__dark.sfa__share__qrbottom__wrap .sfa__logo__dark{display: block;} .sfa__share__qrbottom__dark.sfa__share__qrbottom__wrap .sfa__bottom__part{background: url(//n.sinaimg.cn/finance/2024swhjbj/img/share_pic02.570d245.png) no-repeat bottom;background-size: 750px 176px;} `; // 将CSS字符串添加到style元素中 styleElement.textContent = cssString; // 将style元素插入到head中 document.head.appendChild(styleElement); } } function textLengthToClass(text, { trim = true } = {}) { // 类型与空值容错 if (text == null) return ''; if (typeof text !== 'string') text = String(text); if (trim) text = text.trim(); if (text === '') return ''; // 计算“显示宽度”:中文计 2,其他计 1 let width = 0; for (let i = 0; i < text.length; i++) { const ch = text[i]; // 常见汉字区间(BMP 基本多文种平面) if (/[\u4e00-\u9fa5]/.test(ch)) { width += 2; } else { width += 1; } } // 映射到 class if (width === 8) return 'sfa_share_search_text4'; if (width === 12) return 'sfa_share_search_text6'; if (width === 16) return 'sfa_share_search_text8'; return ''; } /** * 判断输入是否为图片URL,是则返回URL,否则用 awesome-qr 生成 Base64 * @param {string} qrImg - 输入内容(图片URL或任意文本) * @param {Object} [qrOptions] - 可选,awesome-qr 的配置项 * @returns {Promise} - 图片URL 或 Base64 Data URI */ async function handleQrInput(qrImg, qrOptions = {}) { if (typeof qrImg !== 'string' || !qrImg.trim()) { throw new Error('输入必须是非空字符串'); } // 支持的图片扩展名(可按需增删) const imageExts = ['png', 'jpeg', 'jpg', 'gif', 'svg', 'webp', 'bmp']; // 允许路径中带查询参数 ?... 或片段 #... const imageRegex = new RegExp( `\\.(?:${imageExts.join('|')})(?:\\?[^#\\s]*)?(?:#[^\\s]*)?$`, 'i' ); if (imageRegex.test(qrImg.trim())) { return qrImg.trim(); // 认为是图片URL,直接返回 } // 非图片:生成二维码 Base64 await loadScript(SourceList['qr']); const defaultOptions = { text: qrImg, size: 500,//default 400 correctLevel: 0,//纠错级别 default 0 M:15% 0-3 colorDark: '#000000',//前景色 默认000 colorLight: '#fff',//背景色 默认#fff backgroundImage: '',//背景图 gifBackground: '',//gif背景图 logoImage: '',//logo undefined 或者null 表示无logo }; const options = { ...defaultOptions, ...qrOptions }; // const qrCode = await new AwesomeQR.AwesomeQR(options).toDataURL(); const qrCode = await new AwesomeQR.AwesomeQR(defaultOptions).draw() return qrCode; // data:image/png;base64,... } async function appendWrap(text,targetImg,qrImg,isNight){ const tasks = []; const __nightClass = isNight?'sfa__share__qrbottom__dark':''; const __logoSrc = isNight?'//n.sinaimg.cn/finance/cece9e13/20251202/logo_dark.png':'//n.sinaimg.cn/finance/cece9e13/20251202/logo_light.png'; let __shareWrap = document.getElementById('sfa__share__qrbottom__wrap'); //即使wrap已经存在 isNight依然可能改变 if(__shareWrap==null){ // 生成HTML字符串 const __class = textLengthToClass(text); const __QR = await handleQrInput(qrImg); var htmlString = `
搜索 ${text}
`; // 或者使用 insertAdjacentHTML 插入 document.body.insertAdjacentHTML('beforeend', htmlString); __shareWrap = document.getElementById('sfa__share__qrbottom__wrap'); const _qrImg = document.createElement('img'); _qrImg.src = __QR; document.body.querySelector('.sfa__share_qr').appendChild(_qrImg); tasks.push(imgPromise(_qrImg)); }else { // 2. wrap已存在:更新夜间模式类(避免重复添加,同时移除相反类) __shareWrap.classList.toggle('sfa__share__qrbottom__dark', isNight); // 可选:如果text可能变化,更新搜索文本 // __shareWrap.querySelector('.sfa__search__bar').textContent = `搜索 ${text}`; // __shareWrap.querySelector('.sfa__search__bar').className = `sfa__search__bar ${__class}`; } // targetImg.style.cssText = ''; // document.body.querySelector('.sfa__target__img').innerHTML = ''; // document.body.querySelector('.sfa__target__img').appendChild(targetImg); // const _logoImg = document.createElement('img'); // _logoImg.src = __logoSrc; // document.body.querySelector('.sfa__logo__img').appendChild(_logoImg); // tasks.push(imgPromise(_logoImg)); // tasks.push(imgPromise(targetImg)); // 3. 安全操作targetImg(先获取容器,避免重复查询document.body) const targetImgContainer = __shareWrap.querySelector('.sfa__target__img'); targetImg.style.cssText = ''; targetImgContainer.innerHTML = ''; // 清空原有内容 targetImgContainer.appendChild(targetImg); tasks.push(imgPromise(targetImg)); // 4. 处理logo图片(避免重复创建,先清空容器) const logoImgContainer = __shareWrap.querySelector('.sfa__logo__img'); logoImgContainer.innerHTML = ''; // 清空原有logo,避免多图叠加 const _logoImg = document.createElement('img'); _logoImg.src = __logoSrc; logoImgContainer.appendChild(_logoImg); tasks.push(imgPromise(_logoImg)); await Promise.all(tasks); } async function loadScript(config) { return new Promise((resolve, reject) => { // 检查是否已经存在相同 id 或 src 的 script 标签 const existingScript = document.getElementById(config.id) || Array.from(document.scripts).find(script => script.src === config.url); if (existingScript) { console.log('脚本已存在,无需重复加载'); resolve(); return; } const script = document.createElement('script'); script.src = config.url; script.id = config.id; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); }); } /** * 规范化 target 为 DOM 元素 * - 默认值为 null * - 字符串:作为 CSS 选择器,返回第一个匹配的元素(未找到返回 null) * - DOM 元素:直接返回 * - 其他类型:返回 null * * @param {Element|string|null} [target=null] - 目标元素或选择器 * @returns {Element|null} */ function resolveTarget(target = null) { // 已经是 DOM 元素(优先用 instanceof;跨窗口回退到鸭子类型) if (target instanceof Element) return target; if ( target != null && typeof target === 'object' && target.nodeType === 1 && // 元素节点 typeof target.ownerDocument === 'object' // 存在关联文档 ) { return target; } // 字符串选择器:返回第一个匹配的元素 if (typeof target === 'string') { const trimmed = String(target).trim(); if (trimmed === '') return null; try { return document.body.querySelector(trimmed) ?? null; } catch (e) { // 选择器语法错误等容错 console.warn('[resolveTarget] 选择器无效,selector="%s"', trimmed, e); return null; } } // 其他类型一律返回 null return null; } /* 工具:把 的加载包装成 Promise */ function imgPromise(img) { return new Promise((resolve) => { if (img.complete) { // 已经加载完 resolve(); } else { img.addEventListener('load', resolve, { once: true }); img.addEventListener('error', resolve, { once: true }); // 错误也放行,避免卡住 } }); } /** * 将指定选择器元素截图 → 放入预设容器 → 对容器再截图并返回 * @param {string|HTMLElement} target - CSS选择器或DOM元素 * @param {Object} [opts] - 选项 * @param {number} [opts.scale=2] - 输出缩放倍率(高清) * @param {string} [opts.format='png'] - 最终返回格式:'png' | 'jpeg' | 'webp' | 'blob' * @param {string} [opts.bg='#fff'] - 背景色(jpeg/webp有效) * @param {number} [opts.quality=0.92] - 图片质量(jpeg/webp有效,0~1) * @param {string} [opts.containerClass='snapdom-wrapper'] - 包裹容器的className * @param {boolean} [opts.appendToBody=false] - 是否将包裹容器追加到body(便于调试) * @returns {Promise} 最终截图的Data URL或Blob */ async function generateShareImage( { target = null,//外部输入的选择器 scale = 1, quality = 1, isNight = false, autoShare = true,//自动拉起jsBridge.shareImg text = '酒价内参', qrImg = '',//二维码图片地址或者二维码跳转地址 } = {} ) { try { // 1) 获取目标元素 const el = resolveTarget(target); if (!el) { return; } const bgColor = window.getComputedStyle(el).backgroundColor; console.log(bgColor); // 2) 第一次截图 await loadScript(SourceList['snap']); const capture = await snapdom(el, { scale, embedFonts: true }); const targetImg = await capture.toPng(); // 默认PNG //添加css appendCss(); //添加底部分享,判断传的是ImgUrl还是Link await appendWrap(text,targetImg,qrImg,isNight); const wrapper = document.body.querySelector('.sfa__share__qrbottom__wrap'); // 4) 第二次截图:容器 → 返回指定格式 const wrapperCapture = await snapdom(wrapper, { scale, embedFonts: true}); let result; result = await wrapperCapture.toCanvas(); const base64FromCanvas = result.toDataURL('image/png'); // console.log(base64FromCanvas); if(autoShare==true){ //加载jsBridge await loadScript(SourceList['jsbridge']); SFJSBridge.invokeAction('shareImg', { img: base64FromCanvas, }) } return base64FromCanvas; } catch (error) { } } window.generateShareImage = generateShareImage; })()