<template>
<div v-if="riddle2WrapperStyle.display === 'none'" class="rid-load-wrapper">
<div class="rid-load-wrapper__loader">
<p>
<i></i>
<i></i>
<i></i>
</p>
</div>
</div>
<div class="riddle2-wrapper" :style="riddle2WrapperStyle">
<iframe ref="_iframeElement" :style="iframeStyle"
:src="`https://riddle.com/embed/a/${props.riddleId}?lazyImages=${lazyImages}&staticHeight=${staticHeight}`"
allow="autoplay" referrerpolicy="strict-origin"></iframe>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
interface RiddleDataLayerItem {
key: string;
value: string | number;
}
type RiddleEventType =
| "RiddleInited"
| "RiddleNotInited"
| "HeightChanged"
| "PageChanged"
| "Voted";
interface RiddleEvent {
isRiddle2Event: boolean;
type: RiddleEventType;
riddleId: string;
height: number;
isUnrolled: boolean;
redirectToCustomLandingPage?: {
path: string;
data: string;
};
}
enum RiddleTrackEventAction {
Form_Submit = "Form_Submit",
Form_Skip = "Form_Skip",
Block_Submit = "Block_Submit",
Block_Skip = "Block_Skip",
Block_View = "Block_View",
Block_Next = "Block_Next",
Social = "Social",
Cta = "Cta",
CoreMetrics = "CoreMetrics",
LeadSettings = "LeadSettings",
}
enum RiddleTrackNetworks {
customTracking = "customTracking",
facebookPixel = "facebookPixel",
googleAnalytics = "googleAnalytics",
googleAnalytics4 = "googleAnalytics4",
googleTagManager = "googleTagManager",
matomoTag = "matomoTag",
}
enum RiddleTracDefaultkNetworkObjects {
facebookPixel = "fbq",
googleAnalytics = "ga",
googleAnalytics4 = "gtag",
googleTagManager = "dataLayer",
matomoTag = "_paq",
}
interface RiddleTrackNetworksConfig {
networkName: RiddleTrackNetworks;
}
interface RiddleTrackNetworksConfig_Default extends RiddleTrackNetworksConfig {
networkName: RiddleTrackNetworks;
networkObjectName: string;
}
interface RiddleTrackNetworksConfig_Custom extends RiddleTrackNetworksConfig {
networkName: RiddleTrackNetworks;
eventFunction: string;
}
type RiddleTrackEventDataTypes =
| "blockId"
| "blockType"
| "blockTypeGroup"
| "blockTitle"
| "blockDescription"
| "answerIsCorrect"
| "answerScore"
| "answerTotalScore"
| "answerLivesTotal"
| "answerLivesUsed"
| "answerSpotsTotal"
| "answerSpotsFound"
| "answer"
| "resultScore"
| "resultTotalScore"
| "resultScorePercentage"
| "personalityScore"
| "personalityTotalScore"
| "personalityScorePercentage"
| "timerRiddleTotalSeconds"
| "timerRiddleCurrentSeconds"
| "timerBlockTotalSeconds"
| "timerBlockCurrentSeconds";
type RiddleTrackEvent = {
[key in RiddleTrackEventDataTypes]: any;
} & {
isRiddle2Event: boolean;
riddleId: string;
category: string;
action: RiddleTrackEventAction;
name: string;
trackNetworks?: Array<RiddleTrackNetworksConfig>;
};
interface Props {
riddleId: string;
autoScroll?: boolean;
autoScrollOffset?: number;
staticHeight?: boolean;
height?: number;
lazyImages?: boolean;
width: string;
}
const props = withDefaults(defineProps<Props>(), {
autoScroll: false,
autoScrollOffset: 0,
staticHeight: false,
height: 400,
lazyImages: false,
width: '640px'
})
const emit = defineEmits(['riddle-loaded'])
const riddle2WrapperStyle = ref({
margin: '0 auto',
maxWidth: '100%',
width: props.width,
display: 'none'
})
const iframeStyle = ref({
width: '100%',
height: '100%',
border: 'none'
})
const _iframeElement = ref<HTMLIFrameElement>()
const isMessageListenerAdded = ref(false)
const _riddleDataLayer = ref<Array<RiddleDataLayerItem>>()
const _scrollPosition = ref(0)
onMounted(() => {
console.log('Riddle Embed mounted');
if (!isMessageListenerAdded.value) {
isMessageListenerAdded.value = true
window.addEventListener('message', onRiddleV2Event, false);
}
_scrollPosition.value = 0;
document.addEventListener('scroll', onScroll);
})
onBeforeUnmount(() => {
window.removeEventListener('message', onRiddleV2Event);
document.removeEventListener('scroll', onScroll);
})
const onScroll = () => {
if (_scrollPosition.value != 0) {
window.scrollTo(0, _scrollPosition.value);
_scrollPosition.value = 0;
}
sendPositionDataToIframe();
}
const validateDataLayerItem = (target: Array<RiddleDataLayerItem>, value: RiddleDataLayerItem): boolean => {
const validKeyTypes = ['string', 'number'];
let isValid = true;
if (typeof value !== 'object') {
console.log('typeof DataLayerItem:', typeof value);
console.log('DataLayerItem must be an object like: { key: "key", value: "value" || 0 }');
isValid = false;
}
if (typeof value.key !== 'string') {
console.log('typeof DataLayerItem.key:', typeof value.key);
console.log('DataLayerItem must have a "key"');
isValid = false;
}
if (!validKeyTypes.includes(typeof value.value)) {
console.log('typeof DataLayerItem.value:', typeof value.value);
console.log('DataLayerItem must have a "value" of type: ' + validKeyTypes.join(' or '));
isValid = false;
}
if (isValid) {
target.push(value);
}
return isValid;
};
const initDataLayer = () => {
const itemHandler = {
set: (target: Array<RiddleDataLayerItem>, p: PropertyKey, value: RiddleDataLayerItem) => {
if (p === 'length') {
(target as object)[p] = value;
return true;
}
const isValid = validateDataLayerItem(target, value);
if (isValid) {
_iframeElement.value?.contentWindow?.postMessage(
{ dataLayerItem: value, riddleQueryString: window.location.href.split('?')[1], riddleParentLocation: window.location.href },
'*',
);
}
return isValid;
},
};
_riddleDataLayer.value = new Proxy<Array<RiddleDataLayerItem>>([], itemHandler);
readFromUrl();
}
const readFromUrl = () => {
const queryString = window.location.search;
const urlSearchParams = new URLSearchParams(queryString);
const params = Object.fromEntries(urlSearchParams.entries());
for (const key in params) {
if (Object.prototype.hasOwnProperty.call(params, key)) {
const value = params[key];
pushDataLayerItem({
key: key,
value: value,
});
}
}
}
const pushDataLayerItem = (item: RiddleDataLayerItem) => {
if (_riddleDataLayer.value) {
const index = _riddleDataLayer.value.findIndex(i => i.key === item.key);
if (index === -1) {
_riddleDataLayer.value.push(item);
} else {
_riddleDataLayer.value[index] = item;
}
}
}
const UpdateHeight = (height: number) => {
iframeStyle.value.height = height + 'px';
}
const pushTrackEvent = (riddleEvent: RiddleTrackEvent) => {
if (riddleEvent.trackNetworks) {
riddleEvent.trackNetworks.forEach(trackNetwork => {
try {
if (trackNetwork.networkName === RiddleTrackNetworks.customTracking) {
const tnef = (trackNetwork as RiddleTrackNetworksConfig_Custom).eventFunction;
const ef = eval(tnef);
ef(riddleEvent);
} else {
const tn = trackNetwork as RiddleTrackNetworksConfig_Default;
const data = {
...riddleEvent,
} as any;
delete data.trackNetworks;
switch (trackNetwork.networkName) {
// facebook pixel
case RiddleTrackNetworks.facebookPixel:
const fbq = window[tn.networkObjectName];
fbq('trackCustom', riddleEvent.category, {
eventAction: riddleEvent.action,
eventName: riddleEvent.name,
...data,
});
break;
// google analytics
case RiddleTrackNetworks.googleAnalytics:
const ga = window[tn.networkObjectName];
ga('send', 'event', [riddleEvent.category], [riddleEvent.action], [riddleEvent.name], ...data);
break;
case RiddleTrackNetworks.googleAnalytics4:
const gtag = window[tn.networkObjectName];
gtag('event', riddleEvent.category, {
event_name: riddleEvent.name,
event_action: riddleEvent.action,
...data,
});
break;
// google tag manager
case RiddleTrackNetworks.googleTagManager:
const dataLayer = window[tn.networkObjectName];
dataLayer.push({
event: 'RiddleEvent',
event_category: riddleEvent.category,
event_action: riddleEvent.action,
event_name: riddleEvent.name,
...data,
});
break;
// matomo tag
case RiddleTrackNetworks.matomoTag:
const _paq = window[tn.networkObjectName];
_paq.push(['trackEvent', riddleEvent.category, riddleEvent.action, riddleEvent.name, ...data]);
break;
default:
break;
}
}
} catch (error) {
console.log('[RiddleTrackingError] ' + trackNetwork.networkName + ' not found');
}
});
}
}
const sendPositionDataToIframe = () => {
var iframeOffsetTop = _iframeElement.value.getBoundingClientRect().top + window.scrollY || window.pageYOffset;
var viewtop = window.scrollY || window.pageYOffset;
var viewbottom = viewtop + window.innerHeight;
// Post Position to iframe
_iframeElement.value.contentWindow?.postMessage(
{
iframeOffsetTop: iframeOffsetTop,
viewtop: viewtop,
viewbottom: viewbottom,
},
'*',
);
}
const preventSiteJump = () => {
var doc = document.documentElement;
_scrollPosition.value = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);
}
const onRiddleV2Event = (event: MessageEvent) => {
let riddleEvent = event.data as RiddleEvent;
if (!(riddleEvent as RiddleEvent).isRiddle2Event || (riddleEvent as RiddleEvent).riddleId.toString() !== props.riddleId) {
return;
}
// Remove Loader
if ((riddleEvent as RiddleEvent).type === 'RiddleInited') {
riddle2WrapperStyle.value.display = 'block';
emit('riddle-loaded');
}
// Update Height
if ((riddleEvent as RiddleEvent).height) {
UpdateHeight((riddleEvent as RiddleEvent).height);
initDataLayer();
}
if (typeof event.data.category == 'string' && (event.data.category as string).startsWith('RiddleTrackEvent')) {
const data = event.data as RiddleTrackEvent;
pushTrackEvent(data);
}
if (((riddleEvent as RiddleEvent).type == 'PageChanged' || (event.data as RiddleTrackEvent).action === 'Block_View') && props.autoScroll) {
console.log("🚀 ~ onRiddleV2Event ~ props.autoScroll:", props.autoScroll)
// scroll to top of riddle
const pos = _iframeElement.value.getBoundingClientRect().top;
const target = pos + window.scrollY - 10 - props.autoScrollOffset;
window.scrollTo({
top: target,
behavior: 'smooth',
});
sendPositionDataToIframe();
} else if ((riddleEvent as RiddleEvent).type == 'PageChanged' && !props.autoScroll) {
preventSiteJump();
sendPositionDataToIframe();
}
// Redirect to custom landing page
redirectToCustomResultPage(riddleEvent);
};
const redirectToCustomResultPage = (riddleEvent: RiddleEvent) => {
if (
(riddleEvent as RiddleEvent).redirectToCustomLandingPage
) {
const path = (riddleEvent as RiddleEvent).redirectToCustomLandingPage.path;
const data = (riddleEvent as RiddleEvent).redirectToCustomLandingPage.data;
const form = window.parent.document.createElement('form');
form.setAttribute('method', 'post');
form.setAttribute('action', path);
const hiddenField = window.parent.document.createElement('input');
hiddenField.setAttribute('type', 'hidden');
hiddenField.setAttribute('name', 'data');
hiddenField.setAttribute('value', data);
form.appendChild(hiddenField);
window.parent.document.body.appendChild(form);
form.submit();
}
}
</script>
<style lang="scss" scoped>
.riddle2-wrapper {}
.rid-load-wrapper {
position: relative;
margin: 0 auto;
max-width: 100%;
width: 640px;
height: 358.391px;
}
.rid-load-wrapper__loader {
background: transparent;
}
.rid-load-wrapper__loader i {
background: transparent;
}
.rid-load-wrapper__loader i:before {
border-color: #00205b;
}
.rid-load-wrapper__loader {
padding-top: 56%;
border-radius: 5px;
position: absolute;
top: 0;
left: 0;
width: 100%;
transition: opacity ease-out 0.5s;
box-sizing: border-box;
}
.rid-load-wrapper__loader p {
position: absolute;
top: 50%;
left: 50%;
margin: -8px;
}
.rid-load-wrapper__loader i {
position: absolute;
display: block;
width: 20px;
height: 20px;
border-radius: 5px;
left: -8px;
animation: 3s infinite rid-icon;
transform: scale(1) rotate(30deg);
}
.rid-load-wrapper__loader i:before {
box-sizing: border-box;
content: '';
display: block;
position: absolute;
left: 2px;
right: 2px;
bottom: 2px;
top: 2px;
border: 2px solid;
border-radius: 3px;
}
.rid-load-wrapper__loader i+i {
left: 0;
top: -4px;
transform: scale(1) rotate(45deg);
}
.rid-load-wrapper__loader i+i+i {
left: 8px;
top: 0;
transform: scale(1) rotate(60deg);
}
@keyframes rid-icon {
40% {
left: 0;
top: 0;
transform: scale(1) rotate(0);
}
60% {
left: 0;
top: 0;
transform: scale(1) rotate(0);
}
}
</style>