Problem re-using a Class / Not understanding instantiation - javascript

Update - JSFiddle demo here: https://jsfiddle.net/vb2ptmuo/11/
I'm playing around with an interesting effect I came across: https://tympanus.net/codrops/2019/08/07/image-trail-effects/
To start, I have a div containing a bunch of img elements. It dumps them into an array and then creates a trail effect from those images which follows the mouse. You kick this all off via new ImageTrail(".content"). But what if I have more than one set of images and I want to re-trigger it again with those, instead? Example:
<div class="content">
<img src="1.jpg">
<img src="2.jpg">
<img src="3.jpg">
</div>
<div class="content-2">
<img src="4.jpg">
<img src="5.jpg">
<img src="6.jpg">
</div>
Doing a second new ImageTrail(".content-2") does not replace the first set of images with the second set, even though the code reads to me like it should. You still just see the first set of images in the trail.
I'm also slightly concerned with performance if I'm instantiating the ImageTrail class twice (if that's even a thing), but this is wholly secondary to my main issue.
I feel like there's a simple solution but I'm missing it. Scroll down to the bottom of the demo for the commented code "This doesn't work"

The main cause is requestAnimationFrame as part of constructor and render method.
Across instances, for this use case, one instance should have the rendering control using requestAnimationFrame.
I did a trick here, for this use case.
Every time a new instance is created, it will cancel earlier instance's render request (done by requestAnimationFrame) by means of cancelAnimationFrame
Even i cancelled the request done in render method too.
Check out this jsfiddle for modified code : https://jsfiddle.net/rahultarafdar/mfboqy9g/45/

The Code that you posted has one major problem: it uses global variables to store state of the ImageTrail object. This is a pretty bad programming practice as it leads to unexpected behaviour and defeats the purpose of having a class in the first place.
The reason why only the first set of pictures is shown is because both instances of ImageTrail register a callback for the next animation frame with requestAnimationFrame. The callback (render) of the first instance is always called first, because it registered the listener first. This first render function updates the global mousePos variables. The second render function thinks the mouse has not moved, because lastMousePos and mousePos are the same.
Now to address your problem: first you should move all the state that is used by ImageTrail into the class itsef. That includes mousePos, cacheMousePos and lastMousePos. If you have done that successfully, you should get both imagesets diyplaying at the same time, if you have two instances. To activate/deactivate the rendering of ImageTrails, you could add an active attribute, which gets checked at every render call or you could implement methods that cancel/setup the animation frame for a specific instance. (For details how to use cancelAnimationFrame you could look into the answer from rahul or into the MDN)

Related

How to remove an old instance of an image and leave the newly placed version in a repeating code

In my project, I am trying to place "enemies" in random spots on the canvas I am working with. I have got that part down, but I am trying to remove the older instance of the image "enemies" and only have the re-randomized enemy on screen.
Here is the code for the "enemy"
function enemies(){
var movementX= Randomizer.nextInt(midW,width-50);
var movementY= Randomizer.nextInt(midH,0);
var enemyMove= new WebImage("https://codehs.com/uploads/2d57d03e4ed3a0aaa0bc43f4cb37509b");
enemyMove.setSize(50,50);
enemyMove.setPosition(movementX,movementY);
add(enemyMove);
I have this program running every 9 seconds to place another enemy in a different location than the last. I would like to remove the old instance of this image each time the program executes so only the newly positioned "enemy" is on screen. It currently just places "enemies" all over the screen and does not remove the previous instance. If possible I would appreciate any insight into how to remove the old instances of the code running. I have tried putting if(enemyMove == 2){ remove(enemyMove); after add(enemyMove); to see if would remove one instance of enemyMove from the screen once two images were present but that did nothing. I would really appreciate any feedback on how this would work
You should download the image once during a setup phase and either keep the image in some global variable for reuse or pass it as a function argument containing the main loop.

Animate movement of React component from one to another

I'm trying to figure out how to animate moving react component from one to another. For example take very simple, yet interesting card game: you may place any card to a deck, or take top card from the deck.
To make it work, I have 4 classes - <Board> which holds two <Card Collection>: "Deck" and "Hand" components. In constructor, they generate CardModel items and render them via <Card> component. <CardCollection> component has special onCardClick prop which takes callback function. In my case it's onCardClick={/*this is equal to <Board>*/ this.transferCard("Hand")}
Board.transferCard takes clicked CardModel from state of one component and pushes it to another.
The problem is animation - I want card to fly, preferably through center (optional!) from old place to new. I am able to place the newly created Card in "new place" to the same place as old component, but, since i jsut strated to learn React, I'm not sure where exactly I should start. Wrap in ReactCSSTransitionGroup? pass "animate: from{x,y} to{x,y}" property to <CardCollection>?
So the full question is what is the most generic, controllable and "react" way to animate this situation?
JSFiddle base question version: https://jsfiddle.net/fen1kz/6qxpzmm6/
JSFiddle first animation prototype version: https://jsfiddle.net/fen1kz/6qxpzmm6/1
I don't like <this.state.animations.map... here. Also the Animation component looks like an abomination for me, I'm not sure this is the good architecture style for React.
The main mistake I did is try to mix render function with animation. No! Render should not contain anything related to animation (preferably not even starter values), while all the animation should happen after render. The only thing that bothers me is that i still should have state of animations in CardCollection just to throw it into creation of Card
Working example: https://jsfiddle.net/fen1kz/6qxpzmm6/4/
let animation;
if (this.animations[cardModel.id] != void 0) {
animation = this.animations[cardModel.id];
this.animations[cardModel.id] = void 0;
}
...
return <Card
cardModel={cardModel}
onCardClick={onCardClick}
animation={animation}
position={this.getCardPosition(i)}
index={i}
key={cardModel.id}
ref={cardModel.id} // prolly not needed
/>
You can try to use a package I made called react-singular-component, which might actually do what you need.
The component allows you to render a component server times and by a given priority only the highest one will render. Mounting and uncounting the given component will cause the next highest priority to render instead with an animation moving and wrapping the component from its last place and size to the new one.
Here is the Github page: https://github.com/dor6/SingularComponent
Just an idea: You could try to use jQuery animate for animation of moving some HTML element from one place to another. Once animation is complete there is a complete function property only then you could trigger your onCardClick.

Execute a second function when a first function completes W/O a callback parameter

Background: I'm running A/B tests for a website via VWO. I can write a script that is run when the page loads, but I cannot access any of the website's preexisting scripts or alter their code in any way besides "overwriting" in JS (e.g. remove divs, change CSS properties, append divs) after their page loads.
Problem: When the page loads, there is an empty <div id="priceinfo"></div>. Elsewhere on the page, there is an <img ...>. Inside RequestPriceInfo(), the function outputs HTML to div#priceinfo based on certain form field values on the page. It also appends an "order" button (which is actually not a button at all, but an image nested in anchor tags). I'm trying to change the source for the "button" image using JQuery's attr() function.
Progress: None. I have tried using $('a#requestPrice').click(function() {...} ); but it is not working to change the source of the image, I presume because the content has not yet loaded onto the page immediately when a#requestPrice is clicked and my function runs. I need a way to tell when RequestPriceInfo() has been fired and completed, but there is no callback parameter on RequestPriceInfo() and I don't have access to the script to alter it and add one.
Potentially Useful Info: There is a variable, priceRequested, which is changed from false to true when RequestPriceInfo() runs. Though I realize nothing about this solution is going to be elegant, it seems unreasonable to use Object.prototype.watch() to monitor the variable, but I'm running out of ideas.
How can I detect when RequestPriceInfo() has completed to execute my function without editing the function to add a callback parameter?
What about using CSS... your script could add a class to <div id="priceinfo"></div>, like maybe <div id="priceinfo" class="newButton"></div>. Then, in the CSS, you'd hide the img for #priceInfo.newButton and insert your new image as a background image for the anchor.
Here's one way
var oldRequestPrice = RequestPriceInfo;
RequestPriceInfo = function(){
oldRequestPrice();
// Function completed, handle here
}
EDIT
As the question seems to imply that RequestPriceInfo is an asynchronous API call, have a look at this question: Add a "hook" to all AJAX requests on a page
This will keep track of all Ajax requests happening on the page. You can pick out the one you need and fire your function/code on its success.

Toggling an html IMG element's class using JS (no Jquery)

I give up... All of your answers were just different ways of targeting the local element.
If you bothered to actually read what I was saying you would realise that it was not a problem with the code I already had, just that the code DID NOT work on IMG tags.
While faffing around trying to demonstrate my problem (and that none of your solutions did anything different to what was already happening) I found that I can achieve exactly what I want by applying a Grayscale filter to a DIV element placed over each image. The mouseover event then triggers an opacity change in the DIV element.
It is a little heavier that I wanted but it answered my ACTUAL question. The answer being:
Yes, there probably is a way to toggle class of IMG tags. But no, I am probably not going to find it here without causing arguments or being told i'm using "bad code". So yes, it IS easier and more efficient to target DIV elements.
By the way, page load times are about how large data packages are. Larger data packages (images, html/css/js documents, etc) take longer to download and so the page takes longer to load. The website I am trying to create proves this thesis, I have an almost complete and (almost) fully functional website with loads of 'clever' little effects all under 20mb, about 15mb of which is images. This website is clean and simple, is hosted on my Samsung Galaxy Tab 4 (using Papaya) and loads almost instantly.
THIS is what I meant by "I want this to be VERY lite". Thank you all for your attempts to help, it's just a shame that I couldn't get anyone to understand what was going on.
If you add onClick to image element you don't need to pass anything, you will receive MouseEvent which contains all information. You need target from event.
I suggest to not use onClick on element as it is not scalable, you have to add it to all elements. Better to add listener to wrapping/container element and then filter target by some attribute e.g data-something Please check fiddle
So you have wrapping element and you images:
<div class="images-container">
<img src="https://placeholdit.imgix.net/~text?txtsize=33&txt=350%C3%97150&w=350&h=150" data-toggleable class="thumb-gray thumb-color" />
<img src="https://placeholdit.imgix.net/~text?txtsize=33&txt=350%C3%97150&w=350&h=150" data-toggleable class="thumb-gray" />
<img src="https://placeholdit.imgix.net/~text?txtsize=33&txt=350%C3%97150&w=350&h=150" data-toggleable class="thumb-gray" />
</div>
and you attach listener to you wrapping element. It is best practice as you don't attach listeners to each element and same time you are able easily scale your solution
var imagesContainerEl = document.querySelector('.images-container');
imagesContainerEl.addEventListener('click', function(event) {
var element = event.target;
if (element.hasAttribute('data-toggleable')) {
element.classList.toggle('thumb-color');
}
});
The same code can be extended to support mouseover and mouseout. Check fiddle2. One function to rule them all and in the darkness bind them..
var imagesContainerEl = document.querySelector('.images-container');
imagesContainerEl.addEventListener('mouseover', onToggleImage);
imagesContainerEl.addEventListener('mouseout', onToggleImage);
function onToggleImage(event) {
var element = event.target;
if (element.hasAttribute('data-toggleable')) {
element.classList.toggle('thumb-color');
}
}
Also updated fiddle which shows how to make image grayscale/color
Is what you refer to in your question as
onClick="colorFunction(image1)"
an inline javascript event listener?
If so, try replacing it with:
onClick="colorFunction(this)"
and rewrite colorFunction() as:
function colorFunction(image) {
image.classList.toggle('thumb-color');
}

Prototype not defined when accessing children on creation of custom-tag

__What I am trying todo____
Right now I am working with custom HTML5 tags. I am trying to create a tab-control element that is easy to set up. Todo this I create a parent element called 'tab-set' which works much like the 'ul' tag.
To insert tabs, you simply insert 'tab-element' tags (like 'li' tags). The tags can implement own behavior through custom prototypes which extend standardized element-prototypes such as the basic HTMLElement and are then registered with 'document.registerElement()'. At that point there are also opportunities to set callbacks that let you know whenever your element has been created or attached to something, which is what I use to do the necessary calculations on the placement of the individual tabs on the tab-control.
Let me say up-front that I've had trouble with this at first, then got it working, but upon rewriting the whole thing had troubles again for who knows why.
So, in the creation routine of the tab-set I iterate through all the child-tab-elements, calling their custom function 'setDimension', or atleast I am trying to. For some reason Chrome won't initialize the tab-element prototype (setDimension etc) before it has called both 'createdCallback' and 'attachedCallback' on my tab-set. This means that I can't call the child elements custom functions to set it's placement on creation of the tab-set.
Here you have some code samples of what I just described.
simple.html
...
<tab-set>
<tab-element>
<img>guybrush</img>
</tab-element>
<tab-element>
<img>le chuck</img>
</tab-element>
</tab-set>
...
tabs.js
...
tabSet = Object.create(HTMLDivElement.prototype);
tabSet.attachedCallback = function(){
for(/** calculations here **/)
listOfChildren[index].setDimensions(/** placement info **/);
//
// Chrome console: 'setDimensions' is not a function!
//
}
tabElement = Object.create(HTMLDivElement.prototype);
tabElement.setDimensions = function(/** placement info **/){
$(this).css(...);
}
document.registerElement('tab-set',tabSet);
document.registerElement('tab-element',tabElement);
...
The weird thing is that I have a working version of this, and yes, I have tried to emulate it's particular conditions such as for example loading the html-portion through jquery's .load() routine. But no matter what I do, I can not get this to work in my current script. What knowledge am I missing?
Thanks in advance for any help.
__ Solved __
All I had todo was add a link-tag inside the tab-set and have the tab-elements load it's containing style-class. I guess making the tab-elements have a css-class is somehow provoking Chrome to load their prototypes 'prematurely'.

Categories

Resources