I am working on a React AMP project where I need to do some tweaky animation with AMP to show and hide a button when the window is scrolled.
As AMP Animation tag expects an object in the children on <amp-animation> but React does not allows object as it's children.
Here is the code I am trying with:
import React from 'react';
const showAnim = {
"duration": "200ms",
"fill": "both",
"iterations": "1",
"direction": "alternate",
"animations": [
{
"selector": "#download-button",
"keyframes": [
{ "opacity": "1", "visibility": "visible" }
]
}
]
}
const hideAnim = {
"duration": "200ms",
"fill": "both",
"iterations": "1",
"direction": "alternate",
"animations": [
{
"selector": "#download-button",
"keyframes": [
{ "opacity": "0", "visibility": "hidden" }
]
}
]
};
export default class Animate extends React.Component {
componentDidMount() {
window.addEventListener('scroll', this.onScroll)
}
onScroll = () => {
console.log('scrolling')
}
renderShowAnimation = () => <amp-animation id="showAnim" layout="nodisplay" src="">
<script type="application/json">
{showAnim}
</script>
</amp-animation >;
renderHideAnimation = () => <amp-animation id="showAnim" layout="nodisplay">
<script type="application/json">
{hideAnim}
</script>
</amp-animation >;
render() {
return (
<main onScroll={this.onScroll} >
<div>
{this.renderShowAnimation()}
{this.renderHideAnimation()}
<div className="download-button" id="download-button" role="button">
Download
<amp-position-observer
on="enter:hideAnim.start; exit:showAnim.start"
layout="nodisplay">
</amp-position-observer>
</div>
</div>
</main>
)
}
}
Now when I am trying to run the app I am getting the following error:
Objects are not valid as a React child (found: object with keys {duration, fill, iterations, direction, animations}).
I tried to put an onScroll event also but it is also not working in AMP.
If someone has an aleternative or if something is wrong in my code please suggest.
amp-animation expects a JSON as a child, not a Javascript object. (They are similar in syntax, but different.) Even though you wrote the object in JSON syntax, showAnim and hideAnim end up interpreted as objects by javascript. You can convert them to JSON with JSON.stringify().
Simplifying your issue for a second, if you look at one of their examples.
<amp-animation layout="nodisplay">
<script type="application/json">
{
"selector": "#target-id",
"duration": "1s",
"keyframes": {"opacity": 1}
}
</script>
</amp-animation>
The problem is if you paste this into the render method of React, you'll get errors because the { escapes the jsx and it now expects javascript. One way to fix this is to use React's dangerouslySetInnerHTML.
<amp-animation layout="nodisplay">
<script type="application/json" dangerouslySetInnerHTML={{__html:
JSON.stringify(
{
"selector": "#target-id",
"duration": "1s",
"keyframes": {"opacity": 1}
}
)
}}>
</script>
</amp-animation>
In your case, you can change your two functions to
renderShowAnimation = () => <amp-animation id="showAnim" layout="nodisplay" src="">
<script type="application/json" dangerouslySetInnerHTML={{__html: JSON.stringify(showAnim)}}>
</script>
</amp-animation >;
renderHideAnimation = () => <amp-animation id="showAnim" layout="nodisplay">
<script type="application/json" dangerouslySetInnerHTML={{__html: JSON.stringify(showAnim)}}>
</script>
</amp-animation >
AMP not allow define custom javascript (unlimited js) and custom event listener except on attribute). You can implement some dynamic functionality in AMP with amp-bind component and amp-script component
Related
I'm using SASS and React and I have a particleJS library that I incorporated into my project. Everything is working great although I want my project to have a dark mode and I use particleJS as my background. I want to be able to add conditionals to it like when user clicks on dark mode it somehow changes the data in the particles.json file.
Here is a snippet of the json file:
"background": {
"color": {
"value": "#edf2f8"
},
"position": "50% 50%",
"repeat": "no-repeat",
"size": "20%"
}
I'd like to change the bg-color value to darken the background conditionally. If anyone could help that would be great!
This is also my jsx for particle.js
import Particles from "react-tsparticles";
import { loadFull } from "tsparticles";
import particlesOptions from "./particles.json";
import React, { useCallback } from "react";
const ParticleBackground = () => {
const particlesInit = useCallback((main) => {
loadFull(main);
}, []);
const particlesLoaded = (container) => {
console.log(container);
};
return (
<Particles
init={particlesInit}
loaded={particlesLoaded}
options={particlesOptions}
/>
);
};
export default ParticleBackground;
I'm creating a Vue app to read comic books via Reveal.js. The component takes the data from the parent. there is an Axios call in the parent to provide the data from a rest API. I'm also using Vue router with the createWebHashHistory setup as I'm using a Django backend to provide the API.
If I refresh the page it will load the presentation correctly but when I navigate to the page it doesn't seem to initialise Reveal. there are no errors in the console.
I've tried to watch the route changing and other events to run Reveals sync or initialise but I've not had any success.
component
<template>
<div class="reveal" id="comic_box" ref="comic_box">
<div id="slides_div" class="slides">
<section v-for="(page, index) in comic_data.pages" :key="page.index" :data-menu-title="page.page_file_name">
<img :data-src="'/image/' + comic_data.selector + '/' + page.index " class="w-100" :alt="page.page_file_name">
</section>
</div>
</div>
</template>
<script>
import Reveal from "reveal.js";
export default {
name: "TheComicReader",
data () {
return {
}
},
props: {
comic_data: Object
},
methods: {
},
watch: {
'$route' (to, from) {
Reveal.initialize()
}
},
mounted () {
Reveal.initialize()
},
}
</script>
<style scoped>
</style>
comic_data
{
"selector": "e1b76b93-814c-4ee8-9104-8c8187977836",
"title": "Batman 125 (2022) (digital-SD).cbr",
"last_read_page": 0,
"pages": [
{
"index": 0,
"page_file_name": "Batman 125-000.jpg",
"content_type": "image/jpeg"
},
{
"index": 1,
"page_file_name": "Batman 125-001.jpg",
"content_type": "image/jpeg"
}
]
}
After further investigation I noticed that the DOM elements in Reveal were not updating after moving away from the page. I solved this by forcing Reveal to bind to the new comic_box by ref. This is now consistently loading the presentation correctly.
mounted () {
Reveal(this.$refs.comic_box).initialize()
}
This is the pared down code for the main component. I have reactive data elements setup here.
<template>
<div class="scheduler">
<recursive-render :items="data.probeGroupedData"/>
</div>
</template>
<script>
import { ref } from "vue";
import RecursiveRender from "./RecursiveRender.vue";
export default {
name: "Scheduler",
components: {
RecursiveRender
},
setup() {
const data = ref({
probeGroupedData: [],
probeScriptInfo: [],
probeScriptInfoHashById: {},
});
return {
data,
};
},
methods: {
probeSort: function() {
var result = []
//I do various things to sort the data and fill up result and then:
this.data.probeGroupedData = result;
console.log("\nresult:" + JSON.stringify(result, null, ' '));
}
}
},
mounted() {
//I do various things to request the data here, and call this.probeSort
},
};
</script>
The component writes data just fine if I paste the data directly into it on setup.
probeGroupedData: [{
"id": "_1",
"label": "Renton, WA",
"keyName": "id",
"cssClass": "top",
"children": [
{
"label": "Motorola",
"id": "_0",
"cssClass": "manufacturer",
"children": [
{
"label": "Atrix",
"id": "_1",
"cssClass": "family",
"children": [
{
"label": "HD",
"id": "_2",
"cssClass": "model",
"children": [
{
"isLeaf": true,
"id": 1,
"cssClass": "device"
}
]
}
]
}
]
}]
But it isn't updating it when it is being written by probeSort. Do I need to have a watcher or something know that it has changed?
This is the entire code for RecursiveRender
<template>
<div>
<div v-for="item in data.items" :class="cssClass" :key="item.id">
<div v-if="item.isLeaf" :id="item.id">
{{item.label}}
</div>
<div v-if="item.children">
{{item.label}}
<recursive-render :items="item.children" />
</div>
</div>
</div>
</template>
<script>
import { ref } from 'vue';
import RecursiveRender from "./RecursiveRender.vue";
export default {
name: 'RecursiveRender',
components: {
RecursiveRender
},
setup() {
const data = ref({
items: [],
});
return {
data
};
},
props: {
items: {
type: Array,
default: () => []
}
},
mounted() {
//this is where the data is received
this.data.items = this.items;
}
};
</script>
I think the issue may be due to use of ref instead of reactive
ref is for values like boolean, string, and number. It will not update on deep changes. If you have nested data like array or object, reactive might be a better option since it can look for deep data changes.
I also noticed you're not using data.value. this.data.probeGroupedData = result; is likely storing the data in the wrong place. You could try this.data.value.probeGroupedData = result; which may still have issues with reactivity. you could also do this.data.value = {...this.data.value, probeGroupedData : result} which will trigger the reactivity without worrying about the depth issue.
Thanks for the clues that Daniel gave me. The error is passing the value on mount. Just having the prop is enough, it doesn't have to be passed back to data.
<template>
<div>
<div v-for="item in items" :class="cssClass" :key="item.id">
<div v-if="item.isLeaf" :id="item.id">
{{item.label}}
</div>
<div v-if="item.children">
{{item.label}}
<recursive-render :items="item.children" />
</div>
</div>
</div>
</template>
<script>
import RecursiveRender from "./RecursiveRender.vue";
export default {
name: 'RecursiveRender',
components: {
RecursiveRender
},
props: {
items: {
type: Array,
default: () => []
}
},
};
</script>
With more experience, I've learned you need to use provide / inject.
In the parent component, the data you want to have reactive needs to be provided:
//add provide to the import:
import { provide, ref } from "vue";
//in setup() you need to pass this to provide:
setup() {
const boardData = ref ({ <your data here> });
provide('boardData', boardData);
return {
boardData,
<and anything else>
}
}
In the child component you need to add inject to the import:
import { ref, inject } from "vue";
In setup you inject it using the same name as you used to provide it:
setup() {
const boardData = inject('boardData');
return {
boardData,
//and whatever else you need for the component
}
}
This is being used now for a backgammon game that has child pips, and it works well. For layout reasons the pips need to be in four groups of six, so I used a child component for them so I didn't have to have complex repeated loops in the main code. The pips now behave as they are all on rendered in the parent, and changes that the pips do to the reactive data are rendered properly in other sets of pips.
I'm using ParticleJS React component with gatsby.
I start with fetching npm install react-particles-js.
I started with a very simple example :
import React from "react"
import Particles from 'react-particles-js';
export default () => (
<div>
<Particles params={{
"particles": {
"number": {
"value": 50
},
"size": {
"value": 3
}
},
"interactivity": {
"events": {
"onhover": {
"enable": true,
"mode": "repulse"
}
}
}
}}/>
</div>
)
The rendered HTML is something like this :
<div>
<div id="tsparticles">
<canvas class="tsparticles-canvas-el" width="3584" height="1792" style="width: 100%; height: 100%;">
</canvas>
</div>
</div>
Still, nothing is displayed! Am I missing something here?
Everything already worked as expected. Just be careful that by default the particles displayed by the canvas are white. Hence, they will be invisible if the background is white as in my case.
Changed the particles to black and they appeared. Below a minimal working example :
<Particles
params={{
particles: {
color: {
value: "#000000"
}
}
}}
/>
That's a known issue using that library with Gatsby js, but there are workarounds, you can check this GitHub issue: https://github.com/Wufe/react-particles-js/issues/23
And here's a reproduction: https://codesandbox.io/s/goofy-lake-i0c7z?file=/src/pages/index.js
wrap it wit return like
export default () => (
return (
/** yours here)
)
I am getting an error in the Chrome Dev Tools Console when using the react-text-marquee module in react.
I am not sure how to resolve this issue without changing the output to a string instead of JSX.
I should clarify that the functionality of the page is actually working correct, however it would be nice to get rid of errors in case they cause issues down the line.
This is the chrome console error:
09:43:29.747 index.js:2177 Warning: Failed prop type: Invalid prop `text` of type `array` supplied to `Marquee`, expected `string`.
in Marquee (at Session.js:86)
in Session (at Content.js:83)
in div (at Content.js:88)
in Content (at App.js:13)
in div (at App.js:11)
in App (at index.js:9)
__stack_frame_overlay_proxy_console__ # index.js:2177
The complete Session.js code
import React from 'react';
import Marquee from 'react-text-marquee';
import ReactDOMServer from 'react-dom/server';
class Session extends React.Component {
constructor(props) {
super(props);
this.state = {
"showConceptLogo" : true,
"logo" : "logo",
"filmTitle": "2D Justice League",
"classification": "PG",
"sessionAttributes": [
{
"key": "VMAX",
"text": "VMAX",
"color": "yellow",
"background": "red"
},
{
"key": "Gold",
"text": "Gold"
},
{
"key": "Vjnr",
"text": "Vjnr"
},
{
"key": "VPrem",
"text": "VPrem"
},
{
"key": "FWTC",
"text": "FWTC"
},
{
"key": "CC",
"text": "CC"
}
],
"screeningTime": "4:00PM"
}
}
RenderAttributesElement(attr) {
return (
<span style={{color: attr.color, backgroundColor: attr.background}}>{attr.text} </span>
);
}
ConceptLogo(props) {
if (props.display) {
return (
<div className="col-md-1">
<h2>{props.logo}</h2>
</div>
);
}
return null;
}
render() {
return (
<div className="row">
<this.ConceptLogo logo={this.state.logo} display={this.state.showConceptLogo} />
<div className="col-md-6">
<h2>{this.state.filmTitle}</h2>
</div>
<div className="col-md-1">
<h2>{this.state.classification}</h2>
</div>
<div className="col-md-3">
<Marquee hoverToStop={true} loop={true} leading={3000} trailing={3000} text={this.state.sessionAttributes.map(this.RenderAttributesElement)} />
</div>
<div className="col-md-1">
<h2>{this.state.screeningTime}</h2>
</div>
</div>
);
}
}
export default Session;
Both of following options basically just hide the warning.
Option 1: change type of the text prop in runtime:
import PropTypes from 'prop-types';
Marquee.propTypes.text = PropTypes.oneOfType([
PropTypes.string,
PropTypes.array,
]);
However, this might pose a problem if the author of the library decides to make a change that will render your usage of their component incorrect.
Option 2: fork the repository, change propTypes field in source, and, after updating the version in package.json of the library, setup a link to it in your project's package.json:
"react-text-marquee": "git://github.com/yourusername/react-text-marquee"
After that you run npm install and now you have to maintain your copy of the library in case the author does bugfixes or something like that. You might even describe prop type better and make a pull request to the original repository.