Vanta.js not reloading after GSAP & Barba JS transition - javascript

Ok so I got a Vanta.js background running in my <main> which looks awesome. Then I introduced a page transition using Barba and GSAP for animations, which are working fine too. But after going back to my index I found that VantaJS isn't loading again. There are very few questions about Barba and even less answered correctly.
Here's what I've tried until now:
Use window.onload with appendChild to add the vanta libraries each time.
Use Barba hooks to reload the libraries on "after" hook.
Send all scripts to the bottom of my html in correct order.
Here are some SO questions I've used as example:
How to reinit custom js files between pages (Barba.js)?
Scripts are not loading after the page transition in Barba.jS
JS content not working after a page transition using Barba.JS
No luck implementing any of these.
I'm open to other transition libraries if you think that Barba is the problem definitely.
Edit #1
So I found my same issue on BarbaJS Github so I tried implementing this inside my barba.init but still no luck:
async beforeEnter(data) {
const nextEl = data.next.container;
if (nextEl) { //Just a little check to make sure we don't run this on an error
// Find all scripts in the next container
const nextScripts = nextEl.querySelectorAll('script');
//Iterate over incoming script tags
nextScripts.forEach(nextScript => {
const src = nextScript.src;
//Duplicate check - no need to re-execute scripts that are already loaded.
if (document.head.querySelector('script[src="' + src + '"]') == undefined) {
//Have to create a new script element in order for the browser to execute the code
const newScript = document.createElement('script');
newScript.src = src;
newScript.async = true;
document.head.append(newScript);
nextScript.remove(); // Cleaning up the script in the container;
}
})
}
},
Edit #2
An attempt loading inline script (that's the way VantaJS is loaded) but for obvious reasons VANTA isn't defined because I'm calling in from an external js file.
window.Barba.currentInlineScripts = [
VANTA.HALO({
el: "#vanta-canvas",
mouseControls: true,
touchControls: true,
gyroControls: true,
xOffset: 0.18,
scale: window.devicePixelRatio,
scaleMobile: 1.00,
backgroundColor: 0x0A0613,
baseColor: 0x2280D0
})
]
$(function () {
barba.init({
sync: true,
transitions: [
{
afterLeave({
current,
next
}){
if (next.container) {
// Remove old scripts appended to the head
window.Barba.currentInlineScripts.forEach((currentInlineScript) => {
currentInlineScript.remove()
})
// Find all new scripts in the next container
const nextScripts = next.container.querySelectorAll('script');
// Iterate over new scripts
nextScripts.forEach((script) => {
// Check if it is an inline script
if (!script.src) {
// Clone the original script
const newScript = script.cloneNode(true)
// Create a new <script> element node
const newNode = document.createElement('script');
// Assign it innerHTML content
newNode.innerHTML = newScript.innerHTML
// Append to the <head>
const element = document.head.appendChild(newNode)
// Save for later
window.Barba.currentInlineScripts.push(newNode)
}
// Remove the inline script
script.remove()
})
}
},
async leave(data) {
const done = this.async();
pageTransition();
await delay(1000);
done();
},
async enter(data) {
contentAnimation();
},
async once(data) {
contentAnimation();
},
},
],
});
});

Related

Intersection Observer Not Working Wordpress 5.8.1

I implemented Intersection Observer on my Wordpress site which is on dev mode atm and just the last image at the bottom of the site is lazyloading.
Not sure the reason why that is happening. See below my lazy code.
/*Lazy load images*/
const allViews = document.querySelectorAll("[data-src]");
function preloadImage(img) {
const src = img.getAttribute("data-src");
if (!src) {
return;
}
img.src = src;
}
const options = {
root: null,
threshold: 0,
rootMargin: "0px",
};
const callback = function (entries) {
//console.log(entries);
};
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) {
return;
} else {
console.log(entry.target);
preloadImage(entry.target);
observer.unobserve(entry.target);
}
});
}, options);
allViews.forEach((image) => {
observer.observe(image);
});
On another file, I have a jquery script adding the data-src attribute to all images and adding the lazyload class:
/*change img src to img data-src for lazy load*/
$("img").each(function () {
$(this).attr("data-src", $(this).attr("src"));
$(this).addClass('lazyload');
//$(this).removeAttr("src");
//console.log($(this)[0].outerHTML);
});
Any help will be great.
I'm still confused why are you actually using this method because wordpress already has native loading="lazy" support
Javascript might execute in the order it loaded, If the Jquery code
is not loaded before the IntersectionObserver code, Your script
become useless.
you have comment out //$(this).removeAttr("src"); which means, src attribute still exist and the image will be loaded normally. You should uncomment it.
Instead of spending time developing a lazyload you could simply leave to wordpress do it natively lazy load, or use Plugins like W3 Total Cache, which has inbuilt support for Lazy Loading.

Dynamically loaded CSS in React not working intermittently

I've made a "pluggable" system in React, which dynamically runs tiny "apps" which consist of an HTML, JS and CSS file. The HTML and CSS files are optional. They intercommunicate through the window object.
I'm dynamically loading the three files here, but I'm having the problem that my CSS classes fail to work 1/5 of the time. They don't even seem to get parsed since I cannot manually apply them in Chrome devtools either.
I've tried using both link and style tags to load the CSS, but both have the same problem. Even a 1000ms setTimeout between the CSS and HTML injection doesn't help. CSS parsing consistently fails roughly every third time the component mounts..
I've tried Chrome, Firefox, and Safari. Same problem in all three.
I'm kind of stuck, I'd love to get some feedback on this..
Here is a video of the issue: (the "app" here is a simple SVG file viewer) http://www.giphy.com/gifs/dvHjBBolgA1xAdyRsv
const windowInitialized = useElementBlockInitialization({
id: elementBlockID,
payload: payload,
onResult: onResult
});
const [styleAndHTMLInitialized, setStyleAndHTMLInitialized] = useState(false);
// after some properties are set in Window, run this effect
useEffect(() => {
let gettingStyleAndHTML = false;
if (windowInitialized) {
gettingStyleAndHTML = true;
getStyleAndHTML().then(({ styleBody, htmlBody }) => { // async function that fetches some html and css as a string (both potentially null)
if (gettingStyleAndHTML) {
if (styleBody) {
const styleElement = document.createElement('style');
styleElement.type = 'text/css';
styleElement.appendChild(document.createTextNode(styleBody));
document.head.appendChild(styleElement);
}
if (htmlBody) {
// containerElement is a ref
containerElement.current.innerHTML = htmlBody;
}
setStyleAndHTMLInitialized(true);
}
});
}
return () => {
gettingStyleAndHTML = false;
};
}, [windowInitialized]);
// after the CSS and HTML is injected, run this hook
useEffect(() => {
if (styleAndHTMLInitialized) {
const scriptElement = document.createElement('script');
scriptElement.setAttribute('data-eb-container-id', containerElementID);
scriptElement.setAttribute('data-eb-id', elementBlockID);
scriptElement.setAttribute('src', makeElementBlockBaseURL() + '.js');
document.head!.appendChild(scriptElement);
return () => {
scriptElement.remove();
};
}
return;
}, [styleAndHTMLInitialized]);
// only render the container once the window properties are set
return windowInitialized ? (
<Container ref={containerElement} id={containerElementID} />
) : null;
I figured it out.
My automatically generated class names occasionally started with a number. CSS class names can not apparently start with a number!
Do'h.

VueJS: How to dynamically execute Javascript from string?

I'm writing a website using VueJS which allows (selected) users to add scripts that are automatically executed upon page load. Here's a sample text that a user might upload:
<script src="https://cdnjs.cloudflare.com/ajax/libs/howler/2.0.5/howler.js"></script>
<script>
var sound = new howler.Howl({
src: ['./sample.mp3']
)}.play();
</script>
This text is stored into a string after retrieving from API backend. The problem now is: I couldn't get it to execute however I try. Is there an option in VueJS that can automatically execute javascripts in strings?
As a reference, here's my code:
var temp_arr = utils.preprocess(vm.chapterInfo.Content)
vm.display = temp_arr[0]
vm.control_script = console.log(temp_arr[1])
// None of below worked
eval(vm.control_script)
document.getElementsByTagName("head")[0].appendChild(control_script)
The problem isn't a Vue one, but a JavaScript one.
I assume that you already understand the security implications of allowing users to run JavaScript; it's rarely a good idea. Sites like JSFiddle do it successfully, however it will take a lot of work and understanding to make it safe, so if you're not 100% sure with what you are doing, then as #WaldemarIce said, you shouldn't do it!
Right, with the warning out the way, you need to do a few things to get this to work:
1) Load the external scripts:
loadScripts() {
return new Promise(resolve => {
let scriptEl = document.createElement("script");
scriptEl.src = "https://cdnjs.cloudflare.com/ajax/libs/howler/2.0.5/howler.js";
scriptEl.type = "text/javascript";
// Attach script to head
document.getElementsByTagName("head")[0].appendChild(scriptEl);
// Wait for tag to load before promise is resolved
scriptEl.addEventListener('load',() => {
resolve();
});
});
}
Here I'm simply attaching the external script to the head of the document and attaching a load event, which resolves the Promise when loaded.
2) Now we have loaded the external script we can execute the remainder of the script. You will need to strip out the script tags, so you can do something like this:
executeScript() {
// remove script tags from string (this has been declared globally)
let script = string.replace(/<\/?script>/g,"")
eval(script)
}
Form the Vue perspective, you can then execute this inside the created hook:
created() {
this.loadScripts().then(() => {
this.executeScript();
});
},
I'll leave it to you to extract the external scripts you want to load from your user input, but here's a JSFiddle: https://jsfiddle.net/49dq563d/
I recently came across this problem and had to extend on the answer from #craig_h. The example below allows full embed code to be sent through as string (HTML elements as well as scripts and inline JS). This is using DOMParser.
<div ref="htmlDump"></div>
<script>
import Vue from "vue";
export default {
...
methods: {
cloneAttributes(element, sourceNode) {
let attr;
let attributes = Array.prototype.slice.call(sourceNode.attributes);
while(attr = attributes.pop()) {
element.setAttribute(attr.nodeName, attr.nodeValue);
}
}
},
mounted(){
if(this.embedString && this.embedString.length > 0)
{
//Parse the code given from the API into a new DOM so we can easily manipulate it
var parser = new DOMParser();
var htmlDoc = parser.parseFromString(this.embedString, 'text/html');
//Get the contents of the new DOM body and loop through.
//We want to add all HTML elements to the page and run / load all JS
var kids = [...htmlDoc.body.children];
let len = kids.length;
for (var i = 0; i < len; i++) {
var item = kids[i];
if(item.tagName == "SCRIPT")
{
//If we have a 'src' attribute then we're loading in a script
if(item.hasAttribute('src'))
{
//Create a new element within the current doc to trigger the script load
let scriptEl = document.createElement("script");
//Copy all attributes from the source element to the new one
this.cloneAttributes(scriptEl, item);
//Attach script to the DOM to trigger it to load
this.$refs.htmlDump.appendChild(scriptEl);
} else {
//if we don't have a 'src' attribute then we have some code to run
eval(item.innerText);
}
} else{
this.$refs.htmlDump.appendChild(item);
}
}
}
}
...
}
</script>

Is it efficient to scan the page for a keyword every second?

So I am working on a personal userscript for myself and a few friends. The userscript is used to block keywords on a page.
To search for the keywords on the page I use:
var filter = ["worda", "wordb", "wordc"];
var found = false;
var check =
function () {
//Stores the content of the head and body in the document.
var head = $("head").html();
var body = $("body").html();
$.each(filter, function(index, item) {
if ((head + body).toString().toLowerCase().contains(item.toLowerCase())) {
window.clearInterval(interval);
console.log("Found: " + item);
found = true;
return false;
}
});
if (found) {
$("body").hide();
var originalTitle = $(document).prop("title");
$(document).prop("title", "Blocked");
window.setTimeout(function() {
if (confirm("This page contains a blocked keyword, are you sure you'd like to continue?")) {
$(document).prop("title", originalTitle);
$("body").show();
return;
}
}, 1);
}
};
And then I set it to repeat every second through:
var interval = window.setInterval(check, 1000);
The reason I re-check the page every second is because new content may have been dynamically created through Javascript, and I need to make sure that it is also filtered.
However, I'd like to know if this is the most efficient way to scan the page for keywords, or if there is a more efficient method which does not require me to re-check the entire page (possibly only need to re-check new elements).
Also I'd like to push the interval as low as I can (I'm thinking around 100ms), however I do not know if this will be very resource-friendly.
Thank you for the help.
You're correct that scanning the content of the page periodically is an inefficient approach to the problem. That being said, if it's only running once a second, it probably isn't a huge deal.
Still, there's a better option. The MutationObserver interface can be used to fire events when a document, or a part of a document, is modified. This is perfect for what you're trying to do:
var observer = new MutationObserver(function() {
… scan the document for your keywords …
});
observer.observe(document, {
childList: true,
characterData: true,
subtree: true
});

Unable to Lazy load Images

Well I am unable to lazy load images for some reason. Here is the waterfall view of the tested site
Apparently I am using two lazy load scripts with lazy load script 1 which works but kills the lightbox plugin and also requires lots of tweaking using data-src and src and class="lazy-load" attributes. which I am using for non post related images.
But the main problem lies in the second script which requires Jquery but doesn't require any tweaking with the images. The script is called stalactite (via Github) which I am using like this
<script charset='utf-8' src='http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js' type='text/javascript' />
<script href='http://googledrive.com/host/0B2xMFcMT-eLiV1owdlBMeklGc2M' type='text/javascript' />
<script type='text/javascript'>
$( 'body').stalactite({
loader: '<img/>' // The contents of the loader. Defaults to a dataURL animated gif.
});
</script>
You can find the link of the stalactite script in the above code and here is the documentation.
I don't know why it doesn't work ! Or am I executing it wrongly.
A solution to the problem will be very helpful. Many thanks in advance.
If you are tired of trying to use lazy load libraries and haven't managed to make all of it work, I can suggest you to create lazy load script on your own, or you can take a look at this code below.
By only using jQuery and without needing any other library, you can use this script to achieve the lazy load (I modified the codes from the original one that I used at work):
var doLazyLoad = true;
var lazyLoadCounter = 0;
var lazyLoadMax = 2; // Maximum number of lazy load done
// Button to indicate lazy load is loading,
// or when lazy load has reached its maximum number,
// this button load data manually.
// You can replace this with something like gif loading animation.
var $btnLoadMore = $("#btn-lazy-load-more");
// I use AJAX function to get the data on lazy load
var ajaxFn = function (enableScrollAnim = true) {
var loadingStr = 'Loading...',
idleStr = 'Load more',
ajaxUrl = 'http://www.example.com/posts',
ajaxHeaders = {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'},
ajaxData = {
start: $('.posts-wrapper .post').length
};
$btnLoadMore.text(loadingStr); // You can disable the button to prevent manual loading
return $.ajax({
url: ajaxUrl,
type: 'GET',
dataType: 'json',
data: ajaxData,
headers: ajaxHeaders,
// On success AJAX, append newly loaded data to lazy load container.
// Here in my example, the GET request returns data.view i.e. the content, and data.total i.e. total number of posts
success: function (data) {
var $newLoadedPosts = $(data.view),
nlsMarginBottom;
$('.posts-wrapper').append($newLoadedPosts);
$newLoadedPosts.css('display', 'none').fadeIn();
// Animate auto scroll to newly loaded content
if (enableScrollAnim) {
$('html, body').animate({
scrollTop: $newLoadedPosts.first().offset().top
});
}
if ($('.posts-wrapper .post').length < data.total) {
$btnLoadMore.text(idleStr); // If you disable the button, enable the button again here
} else {
// Remove the button when there's no more content to load.
// Determined by data.total
$btnLoadMore.remove();
}
},
error: function () {
$btnLoadMore.text(idleStr); // If you disable the button, enable the button again here
}
});
};
// Do the lazy load here
if ($btnLoadMore.length) {
$(window).scroll(function () {
var scrollOffset = (($btnLoadMore.offset().top - $(window).height()) + $btnLoadMore.height()) + 100;
var hasReachedOffset = $(window).scrollTop() >= scrollOffset;
var isLmsBtnExist = $btnLoadMore.length;
if (hasReachedOffset && lazyLoadCounter < lazyLoadMax && doLazyLoad && isLmsBtnExist) {
doLazyLoad = false;
ajaxFn(false).always(function () {
doLazyLoad = true;
lazyLoadCounter++;
});
}
});
}
$btnLoadMore.click(ajaxFn);
And here is the GIF demo image of my working code.
If you need any further explanation, just comment this answer and I will try to help you.

Categories