Seguimiento YouTube vídeos en Administrador de etiquetas de Google es una de las cosas más útiles que puedes hacer en términos de seguimiento. YouTube tiene una maravillosa API que puede aprovechar y convertir los eventos detectados en dataLayer
mensajes.
Existen algunas soluciones realmente buenas para rastrear movies de YouTube en GTM:
Ambos hacen un gran trabajo al rastrear los movies que se han cargado con la página. Sin embargo, ambos tienen dificultades con el seguimiento. dinamicamente vídeos cargados. Eso significa movies que se cargan lentamente, en ventanas emergentes o cuando se hace clic en algún enlace de contenido.
En este artículo, voy a mostrar cómo puedes rastrear movies cargados dinámicamente usando la solución de Bounteous con ligeras modificaciones. Bounteous es mi grupo favorito de geeks en Norteamérica y me sobornaron con su increíble libro justo antes de Navidad, lo que ya sería motivo suficiente para usar su código. Sin embargo, su solución también es muy sólida y realmente se presta para rastrear cosas dinámicas también.
incógnita
El boletín a fuego lento
Suscríbete al Boletín a fuego lento para recibir las últimas noticias y contenido de Simo Ahava en su bandeja de entrada de correo electrónico.
lo que necesitarás
Las modificaciones se realizarán en el código JavaScript en la etiqueta HTML personalizada. En otras palabras, no puedes usar CDN de Bounteous para esto. Tendrá que crear una etiqueta HTML personalizada y copiar y pegar el código en el siguiente capítulo.
En cuanto al resto del materials, simplemente sigue la guía de Bounteous. Todo lo que esto hace es agregar un método que puede activar cuando los movies se cargan dinámicamente. No te preocupes, también te explicaré el método más adelante.
La etiqueta HTML personalizada modificada
Entonces, sigue el guía abundanteevitando la CDN, y cuando llegas al capítulo titulado Instalación del Administrador de etiquetas de Googlelea lo siguiente en su lugar.
Cree una nueva etiqueta HTML personalizada y asígnele un nombre atractivo como Utilidad – Seguimiento de vídeo Bomb Overload Christmas Superior.
Lo siento por eso.
A continuación, agregue el siguiente código.
<script>
// Respectfully copied and modified from
// https://github.com/lunametrics/youtube-google-analytics
//
// Authentic implementation by Bounteous
var ytTracker = (operate( doc, window, config ) {
'use strict';
window.onYouTubeIframeAPIReady = (operate() {
var cached = window.onYouTubeIframeAPIReady;
return operate() {
if( cached ) {
cached.apply(this, arguments);
}
// This script will not work on IE 6 or 7, so we bail at this level if we detect that UA
if( !navigator.userAgent.match( /MSIE (67)./gi ) ) {
init();
}
};
})();
var _config = config || {};
var forceSyntax = _config.forceSyntax || 0;
var dataLayerName = _config.dataLayerName || 'dataLayer';
// Default configuration for occasions
var eventsFired = {
'Play' : true,
'Pause' : true,
'Watch to Finish': true
};
// Overwrites defaults with customizations, if any
var key;
for( key in _config.occasions ) {
if( _config.occasions.hasOwnProperty( key ) ) {
eventsFired( key ) = _config.occasions( key );
}
}
//*****//
// DO NOT EDIT ANYTHING BELOW THIS LINE EXCEPT CONFIG AT THE BOTTOM
//*****//
// Invoked by the YouTube API when it is prepared
operate init() {
var iframes = doc.getElementsByTagName( 'iframe' );
var embeds = doc.getElementsByTagName( 'embed' );
digestPotentialVideos( iframes );
digestPotentialVideos( embeds );
}
var tag = doc.createElement( 'script' );
tag.src = '//www.youtube.com/iframe_api';
var firstScriptTag = doc.getElementsByTagName( 'script' )(0);
firstScriptTag.parentNode.insertBefore( tag, firstScriptTag );
// Take our movies and switch them into trackable movies with occasions
operate digestPotentialVideos( potentialVideos ) {
var i;
for( i = 0; i < potentialVideos.size; i++ ) {
var isYouTubeVideo = checkIfYouTubeVideo( potentialVideos( i ) );
if( isYouTubeVideo ) {
var normalizedYouTubeIframe = normalizeYouTubeIframe( potentialVideos( i ) );
addYouTubeEvents( normalizedYouTubeIframe );
}
}
}
// Decide if the aspect is a YouTube video or not
operate checkIfYouTubeVideo( potentialYouTubeVideo ) {
// Exclude already adorned movies
if (potentialYouTubeVideo.getAttribute('data-gtm-yt')) {
return false;
}
var potentialYouTubeVideoSrc = potentialYouTubeVideo.src || '';
if( potentialYouTubeVideoSrc.indexOf( 'youtube.com/embed/' ) > -1 || potentialYouTubeVideoSrc.indexOf( 'youtube.com/v/' ) > -1 ) {
return true;
}
return false;
}
// Flip embed objects into iframe objects and guarantee they've the correct parameters
operate normalizeYouTubeIframe( youTubeVideo ) {
var a = doc.createElement( 'a' );
a.href = youTubeVideo.src;
a.hostname = 'www.youtube.com';
a.protocol = doc.location.protocol;
var tmpPathname = a.pathname.charAt( 0 ) === '/' ? a.pathname : '/' + a.pathname; // IE10 shim
// For safety causes, YouTube needs an origin parameter set that matches our hostname
var origin = window.location.protocol + '%2Fpercent2F' + window.location.hostname + ( window.location.port ? ':' + window.location.port : '' );
if( a.search.indexOf( 'enablejsapi' ) === -1 ) {
a.search = ( a.search.size > 0 ? a.search + '&' : '' ) + 'enablejsapi=1';
}
// Do not set if testing domestically
if( a.search.indexOf( 'origin' ) === -1 && window.location.hostname.indexOf( 'localhost' ) === -1 ) {
a.search = a.search + '&origin=' + origin;
}
if( youTubeVideo.sort === 'software/x-shockwave-flash' ) {
var newIframe = doc.createElement( 'iframe' );
newIframe.top = youTubeVideo.top;
newIframe.width = youTubeVideo.width;
tmpPathname = tmpPathname.change('/v/', '/embed/');
youTubeVideo.parentNode.parentNode.replaceChild( newIframe, youTubeVideo.parentNode );
youTubeVideo = newIframe;
}
a.pathname = tmpPathname;
if(youTubeVideo.src !== a.href + a.hash) {
youTubeVideo.src = a.href + a.hash;
}
youTubeVideo.setAttribute('data-gtm-yt', 'true');
return youTubeVideo;
}
// Add occasion handlers for occasions emitted by the YouTube API
operate addYouTubeEvents( youTubeIframe ) {
youTubeIframe.pauseFlag = false;
new YT.Participant( youTubeIframe, {
occasions: {
onStateChange: operate( evt ) {
onStateChangeHandler( evt, youTubeIframe );
}
}
} );
}
// Returns key/worth pairs of percentages: variety of seconds to realize
operate getMarks(length) {
var marks = {};
// For full help, we're dealing with Watch to Finish with share seen
if (_config.occasions( 'Watch to Finish' ) ) {
marks( 'Watch to Finish' ) = length * 99 / 100;
}
if( _config.percentageTracking ) {
var factors = ();
var i;
if( _config.percentageTracking.every ) {
factors = factors.concat( _config.percentageTracking.every );
}
if( _config.percentageTracking.each ) {
var each = parseInt( _config.percentageTracking.each, 10 );
var num = 100 / each;
for( i = 1; i < num; i++ ) {
factors.push(i * each);
}
}
for(i = 0; i < factors.size; i++) {
var _point = factors(i);
var _mark = _point + '%';
var _time = length * _point / 100;
marks(_mark) = Math.ground( _time );
}
}
return marks;
}
operate checkCompletion(participant, marks, videoId) {
var length = participant.getDuration();
var currentTime = participant.getCurrentTime();
var playbackRate = participant.getPlaybackRate();
participant(videoId) = participant(videoId) || {};
var key;
for( key in marks ) {
if( marks(key) <= currentTime && !participant(videoId)(key) ) {
participant(videoId)(key) = true;
fireAnalyticsEvent( videoId, key );
}
}
}
// Occasion handler for occasions emitted from the YouTube API
operate onStateChangeHandler( evt, youTubeIframe ) {
var stateIndex = evt.information;
var participant = evt.goal;
var targetVideoUrl = participant.getVideoUrl();
var targetVideoId = targetVideoUrl.match( /(?&)v=((^)*)/ )( 1 ); // Extract the ID
var playerState = participant.getPlayerState();
var length = participant.getDuration();
var marks = getMarks(length);
var playerStatesIndex = {
'1' : 'Play',
'2' : 'Pause'
};
var state = playerStatesIndex( stateIndex );
youTubeIframe.playTracker = youTubeIframe.playTracker || {};
if( playerState === 1 && !youTubeIframe.timer ) {
clearInterval(youTubeIframe.timer);
youTubeIframe.timer = setInterval(operate() {
// Examine each second to see if we have hit any of our share seen marks
checkCompletion(participant, marks, youTubeIframe.videoId);
}, 1000);
} else {
clearInterval(youTubeIframe.timer);
youTubeIframe.timer = false;
}
// Playlist edge-case handler
if( stateIndex === 1 ) {
youTubeIframe.playTracker( targetVideoId ) = true;
youTubeIframe.videoId = targetVideoId;
youTubeIframe.pauseFlag = false;
}
if( !youTubeIframe.playTracker( youTubeIframe.videoId ) ) {
// This video hasn't began but, so that is spam
return false;
}
if( stateIndex === 2 ) {
if( !youTubeIframe.pauseFlag ) {
youTubeIframe.pauseFlag = true;
} else {
// We do not need to fireplace consecutive pause occasions
return false;
}
}
// If we're meant to trace this occasion, fireplace it
if( eventsFired( state ) ) {
fireAnalyticsEvent( youTubeIframe.videoId, state );
}
}
// Fireplace an occasion to Google Analytics or Google Tag Supervisor
operate fireAnalyticsEvent( videoId, state ) {
var videoUrl = 'https://www.youtube.com/watch?v=' + videoId;
var _ga = window.GoogleAnalyticsObject;
if( typeof window( dataLayerName ) !== 'undefined' && !_config.forceSyntax ) {
window( dataLayerName ).push( {
'occasion' : 'youTubeTrack',
'attributes': {
'videoUrl': videoUrl,
'videoAction': state
}
} );
} else if( typeof window( _ga ) === 'operate' &&
typeof window( _ga ).getAll === 'operate' &&
_config.forceSyntax !== 2 )
{
window( _ga )( 'ship', 'occasion', 'Movies', state, videoUrl );
} else if( typeof window._gaq !== 'undefined' && forceSyntax !== 1 ) {
window._gaq.push( ( '_trackEvent', 'Movies', state, videoUrl ) );
}
}
return {
init : init,
digestPotentialVideos : digestPotentialVideos
}
})(doc, window, {
'occasions': {
'Play': true,
'Pause': true,
'Watch to Finish': true
},
'percentageTracking': {
'each': 25,
'every': ( 10, 90 )
}
});
/*
* Configuration Particulars
*
* @property occasions object
* Defines which occasions emitted by YouTube API
* can be changed into Google Analytics or GTM occasions
*
* @property percentageTracking object
* Object with configurations for share seen occasions
*
* @property every array
* Fires an occasion as soon as every share ahs been reached
*
* @property each quantity
* Fires an occasion for each n% seen
*
* @property forceSyntax int 0, 1, or 2
* Forces script to make use of Basic (2) or Common(1)
*
* @property dataLayerName string
* Tells script to make use of customized dataLayer title as an alternative of default
*/
script>
Sí, eso es MUCHAS cosas. Pero tengan paciencia conmigo.
Las modificaciones a la solución de Bounteous son pocas pero significativas.
En primer lugar, en lugar de utilizar un función anónimaen realidad estamos reservando un espacio en el espacio de nombres world y exponiendo la función en una variable llamada ytTracker
.
Hacemos esto porque queremos invocar ciertos métodos en la configuración después de la ejecución inicial. Si no expusiéramos un método público para ello, necesitaríamos ejecutar todo una y otra vez, cada vez que un vídeo se carga dinámicamente, y eso supone una enorme carga para el rendimiento.
Lo siguiente que haremos es agregar una sola línea al normalizeYouTubeIframe()
método:
operate normalizeYouTubeIframe( youTubeVideo ) {
...
youTubeVideo.setAttribute('data-gtm-yt', 'true'); // <--- NEW
return youTubeVideo;
}
El youTubeVideo.setAttribute()
El comando se utiliza para agregar un atributo de datos a todos los iframes que ya han sido tratados por el script. Esto se debe a que queremos evitar ejecutar los métodos de inicialización una y otra vez para vídeos que ya han sido configurados para el seguimiento. En mis pruebas, si un video fue rastreado varias veces, se produjeron conflictos de tiempo de ejecución.
Ahora que el atributo de datos está ahí, debemos verificar su existencia. En el checkIfYouTubeVideo()
método, agregaremos el cheque de esta manera:
operate checkIfYouTubeVideo( potentialYouTubeVideo ) {
if (potentialYouTubeVideo.getAttribute('data-gtm-yt')) {
return false;
}
...
}
Este cheque sólo busca el datos-gtm-yt atributo que agregamos en el paso anterior y, si se encuentra, se omite la inicialización para este video en explicit.
De esta manera sólo los vídeos que tengan no sido tratado todavía será procesado.
Finalmente, necesitamos exponer dos métodos en el ytTracker
interfaz. Estos nos permitirán manejar movies agregados dinámicamente. Hagámoslo al ultimate de la expresión de la función.
var ytTracker = (operate(...) {
...
return {
init : init,
digestPotentialVideos : digestPotentialVideos
}
})(...);
Nosotros devolver un objeto con dos miembros: init
y digestPotentialVideos
. Entonces, cuando llamamos ytTracker.init()
el script básicamente se ejecuta nuevamente y todas las incrustaciones de iframe de YouTube recién agregadas, pero no tratadas, se procesarán y decorarán con rastreadores de eventos.
Si quieres que sea un poco más robusto, puedes usar ytTracker.digestPotentialVideos(iframe)
en cambio. Pasa un objeto iframe como parámetro y el script solo tratará ese video. Esto es mejor ya que no recorrerá todos los iframes de la página y, en cambio, simplemente decorará el que desee.
Finalmente, configure la etiqueta HTML personalizada para que se energetic en un Vista de página/Listo para DOM Desencadenar.
Esos son los cambios, damas y caballeros.
como usarlo
Como se dijo, hay dos métodos nuevos en la ciudad:
ytTracker.init(); // <-- Embellish all new movies on the web page
ytTracker.digestPotentialVideos(iframe); // <-- Solely adorn the iframe that was handed as a parameter
Lo más possible es que hacer que la solución funcione requiera la ayuda del desarrollador. Si sus desarrolladores han agregado algunos cargadores de contenido dinámicos y complejos, que agregan los movies después de que haya finalizado un increíble desvanecimiento de jQuery, deberá cooperar con ellos para agregar el ytTracker.init()
o ytTracker.digestPotentialVideos()
comando en el lugar correcto.
A continuación se muestra un ejemplo de cómo se vería un cargador de contenido modificado:
operate processVideoClick(ytEmbedSrc) {
var video = doc.createElement('iframe');
video.src = ytEmbedSrc;
var content material = doc.querySelector('#content material');
content material.appendChild(video);
window.ytTracker.digestPotentialVideos(video);
}
Después de inyectar el video, llamas ytTracker.digestPotentialVideos()
. Podrías usar el init()
método sin ningún parámetro, pero como ya tienes el elemento iframe a mano, es mejor usar el método más directo.
Resumen
Dado que cargar contenido dinámicamente no tiene estándares, es difícil ofrecer una solución genérica para administrar contenido cargado dinámicamente en su plataforma de análisis o administración de etiquetas. Sin embargo, la solución authentic de Bounteous tiene un muy buen conjunto de herramientas para abordar también movies de YouTube cargados dinámicamente, sin tener que realizar modificaciones drásticas en el código.
La solución de Bounteous es muy versatile y útil. Hacer algunos agujeros en su interfaz para dejar salir algo de luz lo hace aún mejor, en mi humilde opinión.
La forma óptima de trabajar con él es cooperar con sus desarrolladores. Espero que todos hayamos madurado más allá de la noción de GTM es independiente del desarrollador. Entonces, comuníquese con sus desarrolladores, pídales que modifiquen el JavaScript que controla la inyección dinámica de video y solicite que agreguen el easy ytTracker
métodos a los controladores que crean.