Назад

Персоналізація навчання в Articulate Rise 360: практичний гайд від Селезень Олега

Дата публікації: 02.10.2025
Перегляди: 2543

Співробітник відкриває курс і бачить звернення на своє ім’я, текст з урахуванням його гендеру, а іноді навіть свій аватар чи фото. Звучить круто, чи не так? Для слухача це начебто дрібна деталь, а для L&D фахівців – це інструмент, який суттєво підвищує залученість та ефективність навчання. Персоналізовані курси краще утримують увагу й створюють відчуття цінності: «цей курс зроблено саме для мене».

До того ж усі дані вже зберігаються в LMS. Тож достатньо лише налаштувати так, щоб вони автоматично підвантажувались у курс.

Разом з Олегом Селезнем ми вже ділилися досвідом використання користувацьких даних у Articulate Storyline. Тепер розглянемо інший інструмент – Articulate Rise 360, який часто обирають за простоту, швидкість і сучасний дизайн.

У цій статті ми зосередимось на тому, як поєднати зручність Rise із можливостями персоналізації через LMS Collaborator. Своїм досвідом поділиться Олег Селезень – керівник відділу дистанційного навчання компанії VIYAR.

Переходимо від теорії до практики: які дані з LMS Collaborator можна використати для персоналізації курсів у Rise 360?

Олег Селезень – керівник відділу дистанційного навчання компанії VIYAR

blank

Які параметри ми можемо отримати?

Усі дані передаються з LMS Collaborator у SCORM через змінну cmi.core.student_info. Їх також можна використати для персоналізації в Articulate Rise 360.

На відміну від Storyline, у Rise 360 немає прямої роботи з тригерами чи умовами. Проте отримані значення можна застосовувати для персоналізації: налаштовувати сценарії навчання, відображати потрібні блоки та підлаштовувати звернення.

Щоб реалізувати це, знадобиться невелика взаємодія з кодом. Але не хвилюйтеся – все максимально просто.?

blank

Далі покроково, як саме це виглядає на практиці.

Крок 1. Форматування змінних у курсі Articulate Rise 360

Щоб підтягнути дані користувача, достатньо вказати змінну з LMS Collaborator, обрамлену знаками відсотка, наприклад:

  • user_id → %user_id%
  • user_firstname → %user_firstname%
  • user_email → %user_email%

Коли користувач відкриває курс, система автоматично замінює значення на його справжні дані.

Важливо: самі по собі вони не працюють. Якщо не додати код-зв’язку з LMS і не налаштувати публікацію під SCORM, у курсі залишиться просто %user_firstname%.

Що робити далі?

    1. Опублікуйте курс у Rise 360: Publish → LMS, обираємо SCORM 1.2 або 2004 і зберігаємо ZIP.
    2. Розпакуйте архів у будь-яку папку.
    3. Знайдіть та відкрийте папку scormcontent.
    4. Знайдіть файл index.html і відкрийте його у будь-якому редакторі.
    5. Додайте ось цей код одразу після тега <title>:
Код

<script defer src="https://cdn.jsdelivr.net/gh/asimut/user-variables/selezen_v1.js"></script>

Тепер курс підтягуватиме дані користувача.

Крок 2. Фото користувача замість стандартних зображень

Ще один простий спосіб персоналізації – заміна картинок на фото конкретного користувача. Це працює в будь-якому місці курсу, де є зображення.

Це дає відчутну персоналізацію «в обличчя» без зміни верстки блоку та працює в будь-яких місцях курсу, де є зображення.

Як це зробити:

  1. Відкрийте потрібний блок у Articulate Rise 360 та натисните Image Alt Text.
  2. Уведіть replace_user_img та підтвердіть.
  3. За потреби повторіть для інших зображень.

При відкритті курсу система автоматично підставить фото користувача з LMS.

blank

Крок 3. Звернення за гендером

Коли LMS передає інформацію про стать користувача (наприклад, male або female), у курсі ми можемо автоматично змінювати слова та їх закінчення відповідно до потрібного гендера.

Приклад такого текстового блоку в Articulate Rise 360:

blank

На зображені стандартний текстовий блок. Саме у таких блоках можна налаштувати заміну, щоб звернення виглядали природно: «ти завершив» або «ти завершила».

Для цього потрібно підготувати невеликий скрипт, який перевіряє отримане значення і підставляє потрібний варіант.

На що варто звернути увагу:

За замовчуванням усі звернення можна залишати у чоловічому роді. Потім система буде змінювати їх на потрібний варіант у тих блоках, де ви це вкажете. Важливо не підключати заміну скрізь, інакше можуть випадково змінитися слова поза контекстом звернень. Це виглядатиме як помилка.

Як це зробити:

1. Додаємо код, що отримує дані з LMS.
2. Додаємо модуль для коректних звернень за гендером:

– підключаєте основний скрипт із логікою заміни (він виконує пошук і підміну);
– у конфігурації задаємо слова, які будуть замінюватися (наприклад, завершив → завершила) та ID блоків (targetBlocks), де ці заміни дозволені.

У результаті система автоматично підставлятиме правильні варіанти звернень залежно від гендеру користувача.

Код

<script defer src="https://cdn.jsdelivr.net/gh/asimut/user_gender_replacer/selezen_v1.js"></script>

<script>
  // Конфіг (тільки дані, ніякої логіки)
  window.GenderReplacer = {
    debug: false,
    genderReplacements: {
      female: {
        'завершив': 'завершила',
        'дізнався': 'дізналася',
        'ознайомився': 'ознайомилася',
        'зрозумів': 'зрозуміла',
        'пройшов': 'пройшла',
        'успішно завершив': 'успішно завершила',
        'готовий': 'готова',
        'вивчив': 'вивчила',
        'переглянув': 'переглянула',
        'опанував': 'опанувала',
        'засвоїв': 'засвоїла',
        'досяг': 'досягла',
        'здобув': 'здобула',
        'отримав': 'отримала',
        'виконав': 'виконала',
        'склав': 'склала',
        'закінчив': 'закінчила'
      }
    },
    targetBlocks: [
      'cmckqk51l001c357b79yaongk' 
    ]
  };
</script>

Важливо: нижній блок – це лише конфігурація (дані). Саму підміну виконує основний скрипт із логікою заміни, підключений рядком вище.

Робота з ідентифікаторами блоків

Щоб система знала, у якому саме текстовому блоці потрібно робити заміни, ви вказуєте його у списку targetBlocks. Для цього кожен блок у Rise має свій унікальний ID.

Є два способи знайти ID:

Спосіб 1.
Через інструменти розробника DevTools. Якщо ви впевнено користуєтеся консоллю браузера, відкрийте код сторінки й скопіюйте потрібний ідентифікатор.

Спосіб 2.
За допомогою плагіна. Зручніше і швидше, особливо якщо не хочете працювати з кодом напряму.

Також ви можете розширювати список слів для заміни. Якщо серед наведених прикладів немає потрібних, сміливо додавайте власні варіанти у конфігурацію.

Щоб спростити пошук ідентифікаторів блоків, зручно користуватися плагіном.

Як встановити плагін:

  1. Відкрийте браузер і перейдіть у розділ Розширення – Керувати розширенями.
  2. Увімкніть Режим розробника.
  3. Завантажте архів із плагіном, розпакуйте його й додайте розширення з цієї папки у браузер;
    Посилання на архів
  4. Після цього він автоматично з’явиться у вашій сторінці редагування курсу.

Плагін можна вмикати або вимикати будь-коли. З його допомогою ви швидко знаходите ID блоків і додаєте їх у конфігурацію.

blank

Коли ми навчилися швидко знаходити ID за допомогою плагіна, можемо використати ці ідентифікатори не лише для точкових замін, а й для керування видимістю цілих фрагментів курсу. Це дозволить показувати різним департаментам свій контент у межах одного курсу – без дублювання матеріалів і зайвих копій.

Крок 4. Керування контентом для різних департаментів

Ми вже маємо всі змінні з LMS, зокрема user_department. Це означає, що департамент не потрібно шукати: система зчитує його з даних користувача і на цій основі відкриває потрібні блоки курсу. Для цього потрібно лише вказати назву департаменту, який є в LMS та перелік блоків, які він має бачити. Для всіх інших працює гілка default.

Код

<script>
  (function () {
    'use strict';
  
    // Конфіг
    // Заповни mapping під свої департаменти та їхні data-block-id
    window.DepartmentBlocks = window.DepartmentBlocks || {
      debug: false,
      mapping: {
        'назава департаменту':    ['ТУТ ВКАЖИТИ ІD БЛОКУ'], // назва має буди із меленької літери
        'default':     ['ТУТ ВКАЖИТИ ІD БЛОКУ'],     // блок для всіх інших
      },
    };
  
    const DB = window.DepartmentBlocks;
  
    const ATTR_CANDIDATES = ['data-block-id','data-blockid','data-block','blockid','data-id','id'];
    const HIDE_CLASS = 'dept-hidden-by-mapping';
    const STYLE_ID = 'dept-visibility-style';
    const OBS_CONFIG = { childList: true, subtree: true, attributes: false, characterData: false };
  
    let allListedIds = new Set();      // усі ID, що зустрічаються у mapping (для первинного приховування)
    let allowedIds = new Set();        // ID, дозволені для поточного департаменту
    let currentDept = null;            // нормалізоване значення поточного департаменту
    let initialized = false;           // щоб не ініціалізувати двічі
    let mainObserver = null;
  
    const log = (...args) => { if (DB.debug) console.log('[DepartmentBlocks]', ...args); };
    const warn = (...args) => console.warn('[DepartmentBlocks]', ...args);
  
    function normalize(str) {
      return (str == null) ? '' : String(str).toLowerCase().trim();
    }
  
    function injectStyleOnce(doc = document) {
      if (doc.getElementById(STYLE_ID)) return;
      const style = doc.createElement('style');
      style.id = STYLE_ID;
      style.textContent = `
        .${HIDE_CLASS} { display: none !important; }
      `;
      doc.head.appendChild(style);
    }
  
    function getBlockIdFromElement(el) {
      for (const attr of ATTR_CANDIDATES) {
        if (el.hasAttribute && el.hasAttribute(attr)) {
          const v = el.getAttribute(attr);
          if (v && v.trim()) return v.trim();
        }
      }
      return null;
    }
  
    function collectAllListedIds() {
      allListedIds = new Set();
      const map = DB.mapping || {};
      Object.values(map).forEach(arr => {
        (arr || []).forEach(id => allListedIds.add(String(id)));
      });
      log('All listed IDs:', Array.from(allListedIds));
    }
  
    function computeAllowedIdsFor(dept) {
      const mappingKey = DB.mapping?.[dept] ? dept : 'default';
      allowedIds = new Set(DB.mapping?.[mappingKey] || []);
      log('Allowed IDs for', dept, '(using mapping key:', mappingKey, '):', Array.from(allowedIds));
    }
  
    function isListed(id) {
      return allListedIds.has(id);
    }
  
    function shouldShow(id) {
      return allowedIds.has(id);
    }
  
    function hideEl(el) {
      if (!el.classList.contains(HIDE_CLASS)) {
        el.classList.add(HIDE_CLASS);
      }
    }
  
    function showEl(el) {
      if (el.classList.contains(HIDE_CLASS)) {
        el.classList.remove(HIDE_CLASS);
      }
    }
  
    function initialHideListedBlocks(root = document) {
      const candidates = root.querySelectorAll('*');
      candidates.forEach(el => {
        const id = getBlockIdFromElement(el);
        if (id && isListed(id)) hideEl(el);
      });
    }
  
    function applyDepartmentVisibility(root = document) {
      const candidates = root.querySelectorAll('*');
      candidates.forEach(el => {
        const id = getBlockIdFromElement(el);
        if (!id) return;
  
        if (!isListed(id)) return;
        if (shouldShow(id)) showEl(el);
        else hideEl(el);
      });
    }
  
    function isUserDataReady() {
      const sources = [
        window.UserVariables?.data,
        window.UserVariables2?.data,
        window.userData
      ];
      return sources.some(d => d && typeof d === 'object');
    }
  
    function getUserDepartment() {
      if (DB.overrideDepartment) return normalize(DB.overrideDepartment);
  
      const sources = [
        window.UserVariables?.data,
        window.UserVariables2?.data,
        window.userData
      ];
      for (const d of sources) {
        if (!d || typeof d !== 'object') continue;
        for (const key of ['user_department','department','dept','user_dept','org_unit','division']) {
          if (d[key]) return normalize(d[key]);
        }
      }
      return '';
    }
  
    function processIframe(iframe) {
      try {
        const doc = iframe?.contentDocument;
        if (!doc) return;
        injectStyleOnce(doc);
        initialHideListedBlocks(doc);
        if (currentDept) applyDepartmentVisibility(doc);
        const obs = new MutationObserver(() => {
          if (!currentDept) initialHideListedBlocks(doc);
          else applyDepartmentVisibility(doc);
        });
        obs.observe(doc.documentElement, OBS_CONFIG);
      } catch (e) {}
    }
  
    function watchIframes() {
      const iframes = document.querySelectorAll('iframe');
      iframes.forEach(ifr => {
        if (ifr._deptBound) return;
        ifr._deptBound = true;
        if (ifr.contentDocument && ifr.contentDocument.readyState !== 'loading') {
          processIframe(ifr);
        } else {
          ifr.addEventListener('load', () => processIframe(ifr), { passive: true });
        }
      });
    }
  
    function applyNow(root = document) {
      injectStyleOnce(document);
      watchIframes();
      if (!currentDept) initialHideListedBlocks(root);
      else applyDepartmentVisibility(root);
    }
  
    function startObserver() {
      if (mainObserver) return;
      mainObserver = new MutationObserver(() => applyNow(document));
      mainObserver.observe(document.documentElement, OBS_CONFIG);
      ['#app','.rise-player','[data-rise]','.course-container'].forEach(sel => {
        const c = document.querySelector(sel);
        if (c && !c._deptObserver) {
          const o = new MutationObserver(() => applyNow(c));
          o.observe(c, OBS_CONFIG);
          c._deptObserver = o;
        }
      });
    }
  
    function resolveDepartmentAndApply() {
      const dept = getUserDepartment();
      if (!dept) {
        log('user_department не знайдено (поки що). Працюємо у режимі initial-hide.');
        return;
      }
      if (dept === currentDept) return;
      currentDept = dept;
      computeAllowedIdsFor(currentDept);
      applyNow(document);
    }
  
    function waitForDataThenInit() {
      if (initialized) return;
      initialized = true;
      injectStyleOnce(document);
      collectAllListedIds();
      initialHideListedBlocks(document);
      watchIframes();
      startObserver();
  
      let tries = 0;
      const quick = setInterval(() => {
        tries++;
        if (isUserDataReady()) {
          resolveDepartmentAndApply();
          if (currentDept) clearInterval(quick);
        }
        if (tries >= 20) clearInterval(quick);
      }, 500);
  
      setInterval(resolveDepartmentAndApply, 2000);
    }
  
    DB.forceApply = () => applyNow(document);
    DB.setDepartment = (deptName) => {
      DB.overrideDepartment = deptName;
      resolveDepartmentAndApply();
    };
  
    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', () => setTimeout(waitForDataThenInit, 50), { once: true });
    } else {
      setTimeout(waitForDataThenInit, 50);
    }
  })();
</script>

Як це працює:

  • на старті всі «керовані» тимчасово блоки приховані;
  • коли система зчитує user_department, вона показує лише ті блоки, які ви закріпили за цим департаментом;
  • блоки з інших списків залишаються прихованими, а ті, яких немає в mapping – видимі;
  • якщо департамент не знайдено або він порожній – користувач бачить default.

В результаті кожен співробітник отримує тільки свій контент – без дублювання курсу та зайвих версій.

blank

Підсумуємо

Тепер у вас є три готові інструменти:

  • Скрипт №1 – отримує дані користувача, підтягуючи назву підрозділу або персональне фото.
  • Скрипт №2 – змінює слова й закінчення в тексті залежно від гендеру користувача.
  • Скрипт №3 – керує контентом: приховує чи відображає блоки курсу для різних департаментів.

Вам не обов’язково підключати всі одразу – використовуйте тільки ті, що справді потрібні для вашого курсу.

blank

Останній крок: пакування та завантаження курсу в LMS

Коли всі зміни внесені, залишилося зробити найпростіше – правильно запакувати курс.

Як це зробити:

  1. Виділіть усі файли та папки всередині проєкту (але не саму верхню папку).
  2. Запакуйте їх у форматі ZIP за допомогою будь-якого зручного архіватора.
  3. Завантажте отриманий ZIP файл в LMS Collaborator.

blank

Висновок

В статті ми пройшли весь шлях: від того, як підтягувати дані з LMS і підставляти реальні фото, до зміни звернень за гендером і керування блоками курсу для різних департаментів.
Тепер курс працює «з коробки» та перетворюється на багато версій без дублювання та зайвої роботи.

Що ви отримуєте:

  • Персоналізація – звернення по імені, фото користувача, тексти під стать.
  • Контроль над контентом – показуєте різні блоки для різних департаментів.
  • Економія часу – менше ручної роботи, менше дублювання.
  • Зручність для команди – навчання стає зрозумілим, живим і близьким до потреб співробітників.

Головна ідея проста: менше технічних маніпуляцій, більше персоналізації, гнучкості та цінності для всіх.?

У LMS Collaborator ми допомагаємо зробити навчання більш персональним і зручним для кожного співробітника. Платформа дає можливості налаштувати курси під різні ролі, департаменти та потреби команди. Якщо хочете створювати курси, які враховують особливості користувачів і роблять навчання ефективнішим, спробуйте LMS Collaborator.
blank
Atamanenko Katya
Спробуйте LMS Collaborator в дії
Потрібні рекомендації щодо вибору правильних функцій для оцифрування та автоматизації процесів корпоративного навчання? Ми тут, щоб допомогти.
Замовити демо
Або телефонуйте нашому менеджеру
+38(067)217-0440