Skip to main content
Version: latest

自定义 JS

通过自定义 JS,可以在腾讯问卷页面中执行团队自己的脚本,例如修改页面文案、增加少量交互、接入企业自己的统计脚本等。

加载方式​

已开启的 JS 文件会以普通 <script src="..."> 标签插入到页面 <head> 中,并在 生效范围 内的所有页面加载。需要注意:

  • 执行时机很早:脚本在 <head> 中同步执行,此时页面 <body> 尚未解析,腾讯问卷自身的页面内容也还没有渲染,直接查询页面元素会得到 null,需要等待元素出现后再操作(见下方 示例);
  • 是普通脚本,不是模块:不能使用 import / export,顶层声明的变量会成为全局变量,建议把代码包在立即执行函数 (function () { ... })(); 中;
  • 所有页面都会加载:需要在代码中通过 location.pathname 判断当前页面,只在目标页面执行逻辑;
  • 页面内切换不会重新执行:腾讯问卷的部分页面是单页应用,例如投放页中切换「链接分享」「分组投放」等 tab 时页面不会刷新,脚本不会再次执行,需要监听页面变化。

浏览器兼容性​

腾讯问卷支持的浏览器版本为:

EdgeFirefoxChromeSafari
>= 84>= 83>= 84>= 14.1

自定义 JS 会原样下发到浏览器执行,不会经过任何编译转换。如果使用了上述浏览器不支持的语法,脚本会直接报语法错误、整个文件都不会执行。因此上传的必须是兼容以上浏览器版本的代码。

可以使用的语法​

let / const、箭头函数、模板字符串、解构、展开运算符 ...、class(含公共字段)、async / await、可选链 ?.、空值合并 ?? 等在以上版本中均可正常使用。

不能使用的语法与 API​

语法 / API不支持的浏览器
逻辑赋值 ??=、||=、&&=Chrome / Edge 84
类私有字段与私有方法 #field、#method()Firefox < 90;私有方法另不支持 Safari < 15
类静态初始化块 static { }以上所有最低版本
正则后行断言 (?<=...)、(?<!...)Safari < 16.4
import / export、顶层 await自定义 JS 以普通脚本加载,均不支持
String.prototype.replaceAllChrome / Edge 84
Promise.any、AggregateErrorChrome / Edge 84
Array.prototype.at、Object.hasOwnChrome / Edge < 92、Firefox < 90、Safari < 15.4
Array.prototype.findLast、structuredClone以上所有最低版本
提示

页面虽然内置了部分 polyfill,但 polyfill 只能补齐 API,无法补齐语法,且不保证覆盖所有 API,请不要依赖它。

如果习惯使用新语法编写,可以先用 Babel 按腾讯问卷的浏览器范围编译后再上传:

babel.config.json
{
"presets": [
["@babel/preset-env", { "targets": "edge >= 84, firefox >= 83, chrome >= 84, safari >= 14.1" }]
]
}

Babel 默认只转换语法,不会自动补齐 replaceAll 等 API,仍需避免使用上表中的 API。

编写建议​

  • 限定页面:先判断 location.pathname,不在目标页面时直接 return,避免影响其他页面,也减少不必要的性能开销;
  • 等待元素出现:使用 MutationObserver 监听页面变化,元素出现后再操作,不要用固定时长的 setTimeout;
  • 只改内容,不替换节点:腾讯问卷基于 React 渲染,替换或删除 React 管理的节点可能导致页面报错。修改文案时,推荐只修改文本节点的 nodeValue,或通过 CSS 隐藏元素;
  • 捕获异常:用 try...catch 包裹业务逻辑,避免自定义脚本出错影响页面其他功能;
  • 使用稳定的选择器:优先使用语义化的类名,避免使用带随机哈希的类名(如 header_a1b2c3),后者在每次发版后都可能变化;
  • 嵌入 iframe 前确认 CSP 白名单:页面的 CSP 会拦截白名单以外的 iframe,详见 内容限制。

示例:修改链接分享页标题​

需求​

在问卷「投放」的「链接分享」页(路径 /deliver/link),把页面标题「问卷链接与二维码」修改为「链接分享」。

实现思路​

  1. 投放页是独立页面,先判断路径以 /deliver 开头,否则直接退出;
  2. 「链接分享」页的路径为 /deliver 或 /deliver/link;在投放页内切换 tab 不会刷新页面,因此每次页面变化都要重新判断路径;
  3. 脚本执行时页面尚未渲染,用 MutationObserver 监听 document.documentElement 的变化,等标题元素 .share-basic .deliver-view-header--title 出现后再修改;
  4. 只修改标题中文本节点的 nodeValue,并且只在文案为「问卷链接与二维码」时修改,不影响其他语言;
  5. 页面渲染时 DOM 会在短时间内发生大量变化,用 requestAnimationFrame 把同一帧内的多次变化合并成一次处理。

代码​

代码只使用了 ES5 语法与各浏览器均支持的 API,可以直接保存为 .js 文件上传:

deliver-link-title.js
/**
* 将「链接分享」页的标题「问卷链接与二维码」修改为「链接分享」
* 兼容 Edge >= 84、Firefox >= 83、Chrome >= 84、Safari >= 14.1
*/
(function () {
'use strict';

// 投放页是独立页面,从其他页面进入时会重新加载脚本;不在投放页时直接退出
if (!/^\/deliver(\/|$)/.test(window.location.pathname)) {
return;
}

var FROM_TEXT = '问卷链接与二维码';
var TO_TEXT = '链接分享';
// 「链接分享」页的路径:/deliver、/deliver/link
var LINK_PAGE_PATH = /^\/deliver(\/link)?\/?$/;
var TITLE_SELECTOR = '.share-basic .deliver-view-header--title';

function renameTitle() {
// 投放页内切换 tab 不会刷新页面,每次都需要重新判断当前路径
if (!LINK_PAGE_PATH.test(window.location.pathname)) {
return;
}

var title = document.querySelector(TITLE_SELECTOR);
if (!title) {
return;
}

// 只修改文本节点的值,不替换节点,避免破坏 React 对节点的管理
var nodes = title.childNodes;
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
if (node.nodeType === Node.TEXT_NODE && node.nodeValue.trim() === FROM_TEXT) {
node.nodeValue = TO_TEXT;
}
}
}

// 同一帧内的多次 DOM 变化只处理一次
var scheduled = false;
function scheduleRename() {
if (scheduled) {
return;
}
scheduled = true;
window.requestAnimationFrame(function () {
scheduled = false;
try {
renameTitle();
} catch (err) {
console.error('[custom-js] rename deliver title failed:', err);
}
});
}

// 脚本在 <head> 中执行时页面还未渲染;页面渲染、切换 tab 都会触发 DOM 变化
new MutationObserver(scheduleRename).observe(document.documentElement, {
childList: true,
subtree: true,
});
scheduleRename();
})();

链接分享页标题修改为「链接分享」

内容限制​

上传的 JS 需经过审核,审核会拦截存在安全风险的代码,例如:

  • 高危写法,如 document.write、javascript: 伪协议、以字符串形式拼接的 <script> / <iframe> 标签;
  • 跳转到不安全的网站;
  • 在答题端加载钓鱼广告、诱导填写个人信息或诱导下载等内容。

常规的 DOM 操作、事件监听、网络请求等业务代码都可以正常使用。

iframe 受 CSP 严格限制

问卷页面会下发 Content-Security-Policy 响应头,其中 frame-src 只放行白名单内的域名(问卷自身域名,以及优酷、哔哩哔哩等视频播放器域名)。通过 JS 创建的 iframe 如果指向白名单以外的地址,会被浏览器直接拦截:页面上显示为空白,控制台报错 Refused to frame '...' because it violates the following Content Security Policy directive: "frame-src ..."。

如需嵌入第三方页面,请先在浏览器开发者工具的 Network 面板中查看页面响应头 Content-Security-Policy 的 frame-src,确认目标域名已被放行。不在白名单内的页面无法通过自定义 JS 嵌入。