自定义 JS
通过自定义 JS,可以在腾讯问卷页面中执行团队自己的脚本,例如修改页面文案、增加少量交互、接入企业自己的统计脚本等。
加载方式
已开启的 JS 文件会以普通 <script src="..."> 标签插入到页面 <head> 中,并在 生效范围 内的所有页面加载。需要注意:
- 执行时机很早:脚本在
<head>中同步执行,此时页面<body>尚未解析,腾讯问卷自身的页面内容也还没有渲染,直接查询页面元素会得到null,需要等待元素出现后再操作(见下方 示例); - 是普通脚本,不是模块:不能使用
import/export,顶层声明的变量会成为全局变量,建议把代码包在立即执行函数(function () { ... })();中; - 所有页面都会加载:需要在代码中通过
location.pathname判断当前页面,只在目标页面执行逻辑; - 页面内切换不会重新执行:腾讯问卷的部分页面是单页应用,例如投放页中切换「链接分享」「分组投放」等 tab 时页面不会刷新,脚本不会再次执行,需要监听页面变化。
浏览器兼容性
腾讯问卷支持的浏览器版本为:
| Edge | Firefox | Chrome | Safari |
|---|---|---|---|
| >= 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.replaceAll | Chrome / Edge 84 |
Promise.any、AggregateError | Chrome / Edge 84 |
Array.prototype.at、Object.hasOwn | Chrome / Edge < 92、Firefox < 90、Safari < 15.4 |
Array.prototype.findLast、structuredClone | 以上所有最低版本 |
页面虽然内置了部分 polyfill,但 polyfill 只能补齐 API,无法补齐语法,且不保证覆盖所有 API,请不要依赖它。
如果习惯使用新语法编写,可以先用 Babel 按腾讯问卷的浏览器范围编译后再上传:
{
"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),把页面标题「问卷链接与二维码」修改为「链接分享」。
实现思路
- 投放页是独立页面,先判断路径以
/deliver开头,否则直接退出; - 「链接分享」页的路径为
/deliver或/deliver/link;在投放页内切换 tab 不会刷新页面,因此每次页面变化都要重新判断路径; - 脚本执行时页面尚未渲染,用
MutationObserver监听document.documentElement的变化,等标题元素.share-basic .deliver-view-header--title出现后再修改; - 只修改标题中文本节点的
nodeValue,并且只在文案为「问卷链接与二维码」时修改,不影响其他语言; - 页面渲染时 DOM 会在短时间内发生大量变化,用
requestAnimationFrame把同一帧内的多次变化合并成一次处理。
代码
代码只使用了 ES5 语法与各浏览器均支持的 API,可以直接保存为 .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 操作、事件监听、网络请求等业务代码都可以正常使用。
问卷页面会下发 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 嵌入。