Skip to content

Instantly share code, notes, and snippets.

@jaggli
Last active July 9, 2018 13:52
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jaggli/5b2141c60cef3095a51661bac96de183 to your computer and use it in GitHub Desktop.
Save jaggli/5b2141c60cef3095a51661bac96de183 to your computer and use it in GitHub Desktop.
Styled Components vs Hotjar (Sent to Hotjar support at 27. March 2018)
import React from 'react'
const fixHotjar = () => {
// add sync style tag to head
const syncStyles = document.createElement('style')
syncStyles.type = 'text/css'
document.head.insertBefore(syncStyles, document.head.children[0])
// start styles sync
let lastStyles
const updateStyles = () => {
const styleNodes = [].slice.call(document.querySelectorAll('head [data-styled-components]'))
if (!styleNodes.length) { return }
const styles = styleNodes
.map(({ sheet }) => [].slice.call(sheet.cssRules)
.map(rule => rule.cssText)
.join(' ')
)
.join(' ')
if (styles === lastStyles) { return }
syncStyles.textContent = styles
lastStyles = styles
}
// start updating interval
const startInterval = window.setInterval(() => {
if (!window.hj || !window.hj.behaviorData) { return }
window.clearInterval(startInterval)
window.setInterval(() => {
window.requestAnimationFrame(() => {
updateStyles()
})
}, 500)
}, 50)
}
const HotjarFix = props => (
<script id='hotjar-fix' dangerouslySetInnerHTML={{
__html: `(${fixHotjar.toString()})()`
}} />
)
export default HotjarFix
// ----------------------------------------------------------------------------
// This is a failed attempt to send fake mutations to hotjar
// For documentation reasons, this stays here
// ----------------------------------------------------------------------------
// THIS DOESN'T WORK, DON'T USE THIS IN PRODUCTION!!!
import React from 'react'
var fixHotjar = () => {
// add sync style tag to head
const syncStyles = document.createElement('style')
syncStyles.type = 'text/css'
document.head.insertBefore(syncStyles, document.head.children[0])
// style updates helper
let lastStyles = ''
const getStyleUpdates = () => {
const styleNodes = [].slice.call(document.querySelectorAll('head [data-styled-components]'))
if (!styleNodes.length) { return }
const styles = styleNodes
.map(({ sheet }) => [].slice.call(sheet.cssRules)
.map(rule => rule.cssText)
.join(' ')
)
.join(' ')
if (styles === lastStyles) { return }
const ret = {
removed: lastStyles,
added: styles
}
lastStyles = styles
return ret
}
// fake classes
const NodeList = function (...args) { return new Array(...args) }
const MutationRecord = function ({addedNodes, removedNodes}) {
this.addedNodes = addedNodes
this.attributeName = null
this.attributeNamespace = null
this.nextSibling = null
this.oldValue = null
this.previousSibling = null
this.removedNodes = removedNodes
this.type = 'childList'
this.target = syncStyles
}
// define PromotableObserver
const OriginalObserver =
window.MutationObserver ||
window.WebKitMutationObserver ||
window.MozMutationObserver
const PromotableObserver = function (fn) {
const _this = this
_this._promotedMutations = {
list: [],
nodeCache: {}
}
_this._super = new OriginalObserver((mutations, observer) => {
const all = [].concat(
mutations,
_this.takePromotedRecords()
)
fn.call(window, all, observer)
})
console.log('promotable mutation observer initialized')
const startInterval = window.setInterval(() => {
if (!window.hj || !window.hj.behaviorData) { return }
window.clearInterval(startInterval)
window.setInterval(() => {
window.requestAnimationFrame(() => {
const updates = getStyleUpdates()
if (!updates) { return }
let addedNode
if (updates.added) {
addedNode = document.createTextNode(updates.added)
_this._promotedMutations.nodeCache[updates.added] = addedNode
}
let removedNode
if (updates.removed) {
removedNode = _this._promotedMutations.nodeCache[updates.removed] ||
document.createTextNode(updates.removed)
}
const record = new MutationRecord({
addedNodes: new NodeList(addedNode || 0),
removedNodes: new NodeList(removedNode || 0)
})
_this.promoteRecord(record)
})
}, 500)
}, 50)
_this.promoteRecord = function (record) {
console.log('promote', record)
_this._promotedMutations.list.push(record)
window.setTimeout(() => {
console.log(_this._promotedMutations.list.length)
const promoted = _this.takePromotedRecords()
console.log(promoted)
if (promoted.length) {
// make sure, mutation handler is called, if not already by other modifications
fn.call(window, promoted, _this._super)
}
}, 50)
}
_this.takePromotedRecords = function () {
return _this._promotedMutations.list.splice(0, _this._promotedMutations.list.length)
}
_this.takeRecords = function () {
const allRecords = [].concat(
_this._super.takeRecords(),
_this.takePromotedRecords()
)
console.log('take records', allRecords)
return allRecords
}
_this.observe = function (target, options) {
return _this._super.observe(target, options)
}
_this.disconnect = function (target, options) {
return _this._super.disconnect(target, options)
}
}
window.MutationObserver = PromotableObserver
window.WebKitMutationObserver = PromotableObserver
window.MozMutationObserver = PromotableObserver
}
const HotjarFix = props => (
<script id='hotjar-fix' dangerouslySetInnerHTML={{
__html: `(${fixHotjar.toString()})()`
}} />
)
export default HotjarFix
@Ximik
Copy link

Ximik commented Jul 9, 2018

Is this script still necessary and works for the new HotJat script version?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment