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

如何通过GTM跟踪无刷新JavaScript测验的用户答题行为?

问题描述

我有一个基于JavaScript的无刷新测验,核心特点如下:

  • 点击「Next」按钮时无页面重载,仅通过JavaScript替换内容
  • 每个问题的单选按钮ID均为#answer1、#answer2等重复标识,无差异化元素
  • 现有代码无法修改

现需通过GTM(Google Tag Manager)跟踪以下数据:

  1. 用户答题进度
  2. 各问题的访问次数
  3. 正确答案的数量

目前认为缺乏跟踪依据,请问是否可行?

以下是测验的完整代码:

HTML结构

<div class="quiz-container">
  <div id="question-container">
    <h3 id="question">Question</h3>
    <div id="answer-buttons"></div>
  </div>
  <button id="next-btn" class="btn hide">Next</button>
  <div id="result" class="hide"></div>
  <button id="restart-btn" class="btn hide">Restart</button>
</div>

JavaScript代码

const questionContainer = document.getElementById("question-container");
const questionElement = document.getElementById("question");
const answerButtons = document.getElementById("answer-buttons");
const nextButton = document.getElementById("next-btn");
const restartButton = document.getElementById("restart-btn");
const resultDiv = document.getElementById("result");

let shuffledQuestions, currentQuestionIndex, score;

const questions = [
  {
    question: "What is 2 + 2 equal?",
    answers: [
      { text: "4", correct: true },
      { text: "22", correct: false },
      { text: "222", correct: false },
      { text: "2222", correct: false },
    ],
  },
  {
    question: "What does HTML mean?",
    answers: [
      { text: "HyperText Markup Language", correct: true },
      { text: "HighText Machine Language", correct: false },
      { text: "HyperText Machine Language", correct: false },
      { text: "HighText Markup Language", correct: false },
    ],
  },
  {
    question: "What property is used to change the background color?",
    answers: [
      { text: "color", correct: false },
      { text: "bgColor", correct: false },
      { text: "background-color", correct: true },
      { text: "background", correct: false },
    ],
  },
  {
    question: "What HTML element do we put JavaScript in?",
    answers: [
      { text: "<js>", correct: false },
      { text: "<javascript>", correct: false },
      { text: "<script>", correct: true },
      { text: "<scripting>", correct: false },
    ],
  },
];

startQuiz();

function startQuiz() {
  score = 0;
  questionContainer.style.display = "flex";
  shuffledQuestions = questions.sort(() => Math.random() - 0.5);
  currentQuestionIndex = 0;
  nextButton.classList.remove("hide");
  restartButton.classList.add("hide");
  resultDiv.classList.add("hide");
  setNextQuestion();
}

function setNextQuestion() {
  resetState();
  showQuestion(shuffledQuestions[currentQuestionIndex]);
}

function showQuestion(question) {
  questionElement.innerText = question.question;
  question.answers.forEach((answer, index) => {
    const inputGroup = document.createElement("div");
    inputGroup.classList.add("input-group");

    const radio = document.createElement("input");
    radio.type = "radio";
    radio.id = "answer" + index;
    radio.name = "answer";
    radio.value = index;

    const label = document.createElement("label");
    label.htmlFor = "answer" + index;
    label.innerText = answer.text;

    inputGroup.appendChild(radio);
    inputGroup.appendChild(label);
    answerButtons.appendChild(inputGroup);
  });
}

function resetState() {
  while (answerButtons.firstChild) {
    answerButtons.removeChild(answerButtons.firstChild);
  }
}

nextButton.addEventListener("click", () => {
  const answerIndex = Array.from(
    answerButtons.querySelectorAll("input")
  ).findIndex((radio) => radio.checked);
  if (answerIndex !== -1) {
    if (shuffledQuestions[currentQuestionIndex].answers[answerIndex].correct) {
      score++;
    }
    currentQuestionIndex++;
    if (shuffledQuestions.length > currentQuestionIndex) {
      setNextQuestion();
    } else {
      endQuiz();
    }
  } else {
    alert("select an aswer");
  }
});

restartButton.addEventListener("click", startQuiz);

function endQuiz() {
  questionContainer.style.display = "none";
  nextButton.classList.add("hide");
  restartButton.classList.remove("hide");
  resultDiv.classList.remove("hide");
  resultDiv.innerText = `score : ${score} / ${shuffledQuestions.length}`;
}

可行方案:无需修改现有代码的GTM跟踪实现

完全可以实现,核心思路是通过GTM的自定义事件监听、DOM元素提取和全局变量捕获来获取所需数据,具体方案如下:

1. 跟踪答题进度与问题访问次数

实现逻辑

每次问题切换时(包括初始加载、点击Next后加载新问题),捕获当前问题文本,结合用户会话统计访问次数,同时记录当前答题进度。

GTM配置步骤

  • 创建自定义触发器:
    1. 选择「DOM Ready」触发器,触发条件设置为「元素ID」等于question,且「元素可见性」为「可见」(每次问题切换都会更新#question的文本)。
    2. 补充「点击触发器」,触发条件为「元素ID」等于next-btn,捕捉用户点击Next后的问题切换。
  • 创建自定义JavaScript变量:
    编写脚本提取当前问题文本:
    function() {
      return document.getElementById('question').innerText.trim();
    }
    
  • 创建分析标签:
    发送事件到分析工具(如GA4),事件参数包含:
    • event_name: quiz_question_view
    • question_text: 上述自定义变量的值
    • progress: 当前问题序号(可通过统计会话内quiz_question_view事件的次数得到,或用GTM会话变量计数)

2. 跟踪正确答案数量

实现逻辑

用户点击Next时,通过DOM提取选中的选项文本,结合全局变量score或原始问题数据判断是否正确。

GTM配置步骤

方案A:捕获全局score变量(高效)

  • 创建自定义JavaScript变量:
    直接读取全局score值:
    function() {
      return window.score || 0;
    }
    
  • 创建触发器:
    选择「点击触发器」,触发条件为「元素ID」等于next-btn,且点击元素父级包含answer-buttons(确保是答题后的Next点击)。
  • 创建分析标签:
    每次点击Next时发送事件quiz_answer_submitted,参数包含:
    • current_score: 上述score变量的值
    • question_text: 当前问题文本(同步骤1的变量)
    • selected_answer: 选中的选项文本(通过以下脚本提取)
      function() {
        const checkedRadio = document.querySelector('#answer-buttons input[type="radio"]:checked');
        if (checkedRadio) {
          return document.querySelector(`label[for="${checkedRadio.id}"]`).innerText.trim();
        }
        return null;
      }
      

方案B:无全局变量时的判断(兼容复杂场景)

如果全局变量不可用,可通过对比问题文本和选中选项,匹配原始问题数据中的正确答案:

  • 创建自定义变量存储原始问题数据(复制JS中的questions数组):
    function() {
      return [
        {
          question: "What is 2 + 2 equal?",
          answers: [{ text: "4", correct: true }, ...]
        },
        // 复制所有问题数据
      ];
    }
    
  • 编写脚本判断选中答案是否正确:
    function() {
      const currentQuestion = document.getElementById('question').innerText.trim();
      const checkedRadio = document.querySelector('#answer-buttons input[type="radio"]:checked');
      if (!checkedRadio) return false;
      const selectedAnswer = document.querySelector(`label[for="${checkedRadio.id}"]`).innerText.trim();
      const questions = {{原始问题数据变量}};
      const targetQuestion = questions.find(q => q.question === currentQuestion);
      return targetQuestion ? targetQuestion.answers.find(a => a.text === selectedAnswer)?.correct : false;
    }
    
  • 发送事件时携带is_correct参数,后续在分析工具中累计正确次数。

3. 跟踪测验重启与完成事件

  • 测验完成:创建「DOM Ready」触发器,触发条件为「元素ID」等于result且「元素可见性」为「可见」,发送事件quiz_completed,参数包含最终得分(提取#result的文本)。
  • 测验重启:创建「点击触发器」,触发条件为「元素ID」等于restart-btn,发送事件quiz_restarted。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 03:27:06