Why my var is null and the code doesn't work - javascript

I'm starting with the basics in JS and I try to make my first event, change the image if it clicked but it's not working, where's the prob?
let myImage = document.querySelector('img');
myImage.onclick = function() {
let mySrc = myImage.getAttribute('src');
if(mySrc === 'images/coding_icon.png') {
myImage.setAttribute ('src','images/coding2.png');
} else {
myImage.setAttribute ('src','images/coding_icon.png');
}
}
My var myImage return NULL and nothing happen when I click on my image. It seems that the doc...selector don't select the first occur of img. Sorry if I did something wrong with the post, it's my first time posting here.
Thanks

You are probably using querySelector on img before it really loaded into DOM.
Possible solutions:
1) move your script in the body tag after the img tag declaration.
2) use code on event of other tags, for e.g. on a button click
3) you can put a timeout surrounding script so that it run delayed and DOM can be loaded meanwhile

Related

Cannot read property 'complete' of null - complete

I would like to get this effect working on my webpage.
But I already tried this multiple times and failed every time. So, this is the reason why I am going to try it first on a local level. I copied everything you can see on the codepen.io webpage and replaced the both images with my pictures. Further, I added the id id="coverart" to the img and thats it.
But I get an error on this:
if (image.complete) {
The reason for it is:
Cannot read property 'complete' of null
But if I type this in my console:
document.getElementById('coverart');
I get the element back:
<img id="coverart" src="https://gamekeys-shop.de/wp-content/uploads/2017/01/coverart.png">
So, it cannot be null... What could be the reason for it and how can I fix it?
Here you can download the little html, css and js files.
EDIT:
If you insert alert(image), you will get null. If you replace this
if (image.complete) {
start();
} else {
image.onload = start;
}
with this
image.onload = start;
You will get the same null error... Why do I get null... It cannot be null?
There are few possible ways to improve it:
1) Just put your javascript file after the img tag.
2) Or run you code only when document will be loaded.
You can put your jquery definition onto bottom of body tag and use jquery $(document).ready() event like following.
$(document).ready(function(){
var image = document.getElementById("coverart");
if(image.complete){
console.log("loaded");
}
})
<img id="coverart" src="http://via.placeholder.com/350x150"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Since there is no need to wrap your code within a $(document).ready(function(){...}); or $(function(){...}); on codepen.So try doing that. You need jQuery to do that. So just add
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js">
to your HTML head.
$(document).ready(function(){
var image = document.querySelector('img');
var imageCanvas = document.createElement('canvas');
var imageCanvasContext = imageCanvas.getContext('2d');
...
imageCanvasContext.drawImage(image, 0, 0, width, height);
imageCanvasContext.globalCompositeOperation = 'destination-in';
imageCanvasContext.drawImage(lineCanvas, 0, 0);
}
});
Tried that for myself on localhost and it's working fine

Place cursor at the end of text in an iframe body element with JavaScript

I'll start by apologising as this may seem like or actually be a duplicate, but I've tried every solution I've encountered and none seem to be working for me.
In my HTML I have an iframe referencing another HTML document. With JavaScript, at the press of a list of buttons I insert text into the body of that iframe. I also use JavaScript to maintain focus on the iframe body. The problem is that nothing appears to work for me to get the cursor to move to the end of the text each time I press those buttons, it always moves to the beginning.
One of the solutions I've tried was to add this code to the function that handles my button presses:
iFrameBody.focus();
var content = iFrameBody.innerHTML;
iFrameBody.innerHTML = content;
so the function looks like this:
function typeIn(buttonId) {
var iFrameBody = document.getElementById("iFrame").contentWindow.document.body;
iFrameBody.innerHTML += buttonId;
iFrameBody.focus();
var content = iFrameBody.innerHTML;
iFrameBody.innerHTML = content;
}
Something else I tried was, in the HTML file referenced by my iframe I did:
<body onfocus="this.innerHTML = this.innerHTML;"></body>
I tried several other more complicated solutions that frankly I didn't even quite understand to be honest, all to no avail. Any help would be much appreciated.
I figured it out. The issue was that I was using a body element, writing to it's innerHTML and trying to set focus on the body. By simply using a textarea inside my iFrame instead it became very simple and it only required the simplest code.
This to set focus when the page loads:
window.onload = function () {
var iFrameTextArea = document.getElementById("iFrame").contentWindow.document.getElementById("iFrameTextArea");
iFrameTextArea.focus();
}
And then this to set the button to write to the textarea while maintaining focus:
function typeIn(buttonId) {
var iFrameTextArea = document.getElementById("iFrame").contentWindow.document.getElementById("iFrameInput");
iFrameTextArea.focus();
iFrameTextArea.value += buttonId;
}
Super easy!!
Instead of again using textarea in iframe, u can also solve this by using the following code.
var iframeElement = document.getElementById("iFrame").contentWindow.document.body;
iframeElement.focus();
var len = iframeElement.length ;
iframeElement.setSelectionRange(len, len);

How do I make .load() wait till everything is fully loaded? [duplicate]

I have a Python script that is doing some manipulation on a JPEG image. I pass some parameters to this script and call it from my HTML page. The script returns an img src="newimage.jpg tag.
I know how to wait for the reply from the script but I don't know how to tell when the image is fully loaded (when it is, I want to display it). What I get now is the image loading slowly so the user is seeing this "loading" process. Instead, I want to have a msg telling the user to wait while the image is loading, only then I want to display the image.
You can dynamically create a new image, bind something to its load event, and set the source:
$('<img>').bind('load', function() {
$(this).appendTo('body');
}).attr('src', image_source);
Image Loading
Wait for ajaxRequest
The other answers have mentioned how to do so with jQuery, but regardless of library that you use, ultimately you will be tying into the load event of the image.
Without a library, you could do something like this:
var el = document.getElementById('ImgLocation');
var img = document.createElement('img');
img.onload = function() {
this.style.display = 'block';
}
img.src = '/path/to/image.jpg';
img.style.display = 'none';
el.appendChild(img);

windows onload not firing from external script

Okay i know this has been asked a lot but it seems that none of the solutions actually work for me. Here's the situation. I have a webpage that i need to add to an existing site, this site uses a master page which i can not touch. This limits me to using javascripts window.onload because i do not have access to the body tag.
On my page i have linked my external .js file in the head beneath every other external file. Here is an example of what my .js file looks like.
var myobj = null;
(5 or so functions that work properly. Mainly just toggles that show and hide divs. none touch the onload)
function load(){
myobj = document.getElementById("my_element");
alert("test");
}
window.onload = load;
Tried this and the load function never fires as i never get the alert. I've tried commenting out the first line in the load function and only haven't the alert and still nothing. Looking around i also found another way to do it that i tried without success. Everything else is the same except i removed the window.load and added this.
function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function () {
if (oldonload) {
oldonload();
}
func();
}
}
}
addLoadEvent(load);
addLoadEvent(function () {
/* more code to run on page load */
alert("hello2");
});
In this function, neither of the alerts fire. I have also tried placing an alert outside of a function, that one does work.
The browsers i am using are IE 8.0.7600.16385 and Chrome 21.0.1180.60
Let me know if you need more information.
Oh and as a side note, i cannot use jquery or any other javascript library as we are trying to keep this as light as possible. This page also must work in IE8, that is the businesses only supported browser at the moment. It would be nice if it worked in Chrome as well.
EDIT
In case i'm going about this completely the wrong way, what i am trying to do is keep track of the last element i have. I essentially have a page with 2 columns, left side is a menu and right is content. The content is all placed in div's with only one category showing at a time. The way it is supposed to work is when clicking on the menu category it hides the previous content and shows the new content.
I have set my content class to display: none; and have the start_content ID set to display: block;. What the Javascript is supposed to do is initialize my global with the start_content object. When clicking in the menu it calls a function that does an obj.style.display = 'none' and then sets the new obj to display = 'block'. It then takes the new obj and places it in my global variable to be changed on the next menu click.
here's an example without any of the onload functions
var prevContent = document.getElementById("start_content");
function toggle(id) {
prevContent.style.display = "none";
var content = document.getElementById(id);
content.style.display = "block";
prevContent = content;
}
The problem with this is that prevContent is unidentified when it enters the toggle function. I had assumed this was because i am linking this .js file in the head and so the page hasn't loaded my start_content yet which is why i had changed it to declaring the global as a null and then setting up a window.onload to set the appropriate value after it is created.
Adding "Defer" to the script tag in the head ended up doing the trick.

Proper way to reset a GIF animation with display:none on Chrome

Title is self-explanatory, but I'll provide a step-by-step view on the matter. Hopefully I'm not the first one to have noticed this (apparently) bug on Webkit/Chrome.
I want to reset a GIF animation. All of the examples I've seen so far either simply set the src of the image to itself or set it to an empty string followed by the original src again.
Take a look at this JSFiddle for reference. The GIF resets perfectly fine on IE, Firefox and Chrome.
The issue which I have is when the image has display:none on Google Chrome only.
Check this JSFiddle. The GIF resets fine on IE and Firefox before being displayed in the page, but Chrome simply refuses to reset its animation!
What I've tried so far:
Setting the src to itself as in Fiddle, doesn't work in Chrome.
Setting the src to an empty string and restoring it to the default, doesn't work either.
Putting an wrapper around the image, emptying the container through .html('') and putting the image back inside of it, doesn't work either.
Changing the display of the image through .show() or .fadeIn() right before setting the src doesn't work either.
The only workaround which I've found so far is keeping the image with its default display and manipulating it through .animate()ing and .css()ing the opacity, height and visibility when necessary to simulate a display:none behaviour.
The main reason (context) of this question is that I wanted to reset an ajax loader GIF right before fading it in the page.
So my question is, is there a proper way to reset a GIF image's animation (which avoids Chrome's display:none "bug") or is it actually a bug?
(ps. You may change the GIF in the fiddles for a more appropriate/longer animation gif for testing)
The most reliable way to "reset" a GIF is by appending a random query string. However this does mean that the GIF will be redownloaded every time so make sure it's a small file.
// reset a gif:
img.src = img.src.replace(/\?.*$/,"")+"?x="+Math.random();
Chrome deals with style changes differently than other browsers.
In Chrome, when you call .show() with no argument, the element is not actually shown immediately right where you call it. Instead, Chrome queues the application of the new style for execution after evaluating the current chunk of JavaScript; whereas other browsers would apply the new style change immediately. .attr(), however, does not get queued. So you are effectively trying to set the src when the element is still not visible according to Chrome, and Chrome won't do anything about it when the original src and new src are the same.
Instead, what you need to do is to make sure jQuery sets the src after display:block is applied. You can make use of setTimeout to achieve this effect:
var src = 'http://i.imgur.com/JfkmXjG.gif';
$(document).ready(function(){
var $img = $('img');
$('#target').toggle(
function(){
var timeout = 0; // no delay
$img.show();
setTimeout(function() {
$img.attr('src', src);
}, timeout);
},
function(){
$img.hide();
}
);
});
This ensures that src is set after display:block has been applied to the element.
The reason this works is because setTimeout queues the function for execution later (however long later is), so the function is no longer considered to be part of the current "chunk" of JavaScript, and it provides a gap for Chrome to render and apply the display:block first, thus making the element visible before its src attribute is set.
Demo: http://jsfiddle.net/F8Q44/19/
Thanks to shoky in #jquery of freenode IRC for providing a simpler answer.
Alternatively, you can force a redraw to flush the batched style changes. This can be done, for example, by accessing the element's offsetHeight property:
$('img').show().each(function() {
this.offsetHeight;
}).prop('src', 'image src');
Demo: http://jsfiddle.net/F8Q44/266/
Just because I still need this every now and then I figured the pure JS function I use might be helpful for someone else. This is a pure JS way of restarting an animated gif, without reloading it. You can call this from a link and/or document load event.
<img id="img3" src="../_Images/animated.gif">
<a onClick="resetGif('img3')">reset gif3</a>
<script type="text/javascript">
// reset an animated gif to start at first image without reloading it from server.
// Note: if you have the same image on the page more than ones, they all reset.
function resetGif(id) {
var img = document.getElementById(id);
var imageUrl = img.src;
img.src = "";
img.src = imageUrl;
};
</script>
On some browsers you only need to reset the img.src to itself and it works fine. On IE you need to clear it before resetting it. This resetGif() picks the image name from the image id. This is handy in case you ever change the actual image link for a given id because you do not have to remember to change the resetGiF() calls.
--Nico
This solution preloads the gif and takes it out of the dom and then back in the src (thus avoiding another download)
I just tested it using jquery to remove the attribute and it works fine.
Example:
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js" type="text/javascript"></script>
<script>
$(function() {
$('.reset').click(resetGif);
function resetGif()
{
$('.img1').removeAttr('src', '');
}
});
</script>
</head>
<body>
<img class="img1" src="1.gif" />
reset gif
</body>
</html>
This seemed to work for me in Chrome, it runs each time just before I fade in the image and clears then refills the src and my animation now starts from the beginning every time.
var imgsrc = $('#my_image').attr('src');
$('#my_image').attr('src', '');
$('#my_image').attr('src', imgsrc);
I've a button with the an animated no-loop image in it. I just reload the image with some jquery and this seems to be working for me.
var asdf = $(".settings-button img").attr("src");
$(".settings-button img").attr("src", "").attr("src", asdf);
here's my hack for background images:
$(document).on('mouseenter', '.logo', function() {
window.logo = (window.logocount || 0) + 1;
var img = new Image();
var url = "/img/mylogimagename.gif?v=" + window.logocount;
var that = this;
$(img).load(function(){
$(that ).css('background-image','url(' + url + ')');
});
img.src = url;
});
I experienced problems with all of the above solutions. What finally worked was replacing the src temporarily with a transparent 1px gif:
var transparent1PxGif = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
var reloadGif = function(img) {
var src = img.src;
img.src = transparent1PxGif;
img.offsetHeight; // triggers browser redraw
img.src = src;
};
It's been several years and I've decided to revisit this since we have a number of new options at our disposal.
The issue with my previous answer is that it forces a re-download of the GIF every single time you want to re-start it. While that's fine for small files, it's still an overhead that's best avoided if possible.
With that in mind, I've got a new solution that uses AJAX to download the GIF once, and then converts it into a data URL (via a FileReader) and uses that as the source with a random query string attached.
This way, the browser only ever downloads the image once, and can even cache it properly, and the "reset" pulls from that pre-downloaded resource.
The only catch, of course, is that you have to make sure it's properly loaded before you can use it.
Demo: http://adamhaskell.net/misc/numbers/numbers.html
Relevant code:
var url = "something.gif"; // fallback until the FileReader is done
function setup() {
var xhr = new XMLHttpRequest();
xhr.open("GET",url,true);
xhr.responseType = "blob";
xhr.onreadystatechange = function() {
if( this.readyState == 4) {
var fr = new FileReader();
fr.onload = function() {
url = this.result; // overwrite URL with the data one
};
fr.readAsDataURL(this.response);
}
};
xhr.send();
}
function getGIF() {
return url+"?x="+Math.random();
}
Reset gif animation
When the browser render img, the source specified in src attribute is cached into memory for future reuse. This allows to increase the speed of page loading/reloading, as well as reduce the load on the network. And this behavior suits mostly everyone, because in reality, this is the most optimal and demanded option.
However, as always, there are exceptions. I came up with a dataUrl-based animation update option that solves several problems.
Issues solved:
Need to display gif images with animation without a loop (loop = 1), which may have the same src. But when one such picture appears, it is necessary that it play the animation without changing the animation of other pictures with the same src. The same picture should be loaded from server only once. Stackoverflow
Reset gif animation.
Start animation on hover
Reset src attribute
If we use a solution that clears the src attribute of an image, then all images with the same source will replay their animation. Unfortunately, I still did not fully understand why this is happening, but it interferes with correct work.
Cons
Reset animation of all images with the same src.
There are problems in mobile devices
Pros
Easy and fast
Modify url with random query
This solution consists in adding a random query parameter to the end of the src attribute, so that all images will have a different source, and therefore they will animate independently of each other. There is one big fat NO: this will lead to a constant request to the server to download the picture, and therefore they will no longer be cached. And if we need to display 100 identical pictures, then there will be 100 requests to the server. Rough and tough, but it always works.
Cons
Each picture with a unique query will be reloaded from the server.
Pros
Easy and fast
Modify dataUrl (Proposed Solution)
Data URLs, URLs prefixed with the data: scheme, allow content creators to embed small files inline in documents. They were formerly known as "data URIs" until that name was retired by the WHATWG.
MDN
The dataUrl structure from this documentation:
data:[<mediatype>][;base64],<data>
And this is how it is indicated in the specification:
dataurl := "data:" [ mediatype ] [ ";base64" ] "," data
mediatype := [ type "/" subtype ] *( ";" parameter )
data := *urlchar
parameter := attribute "=" value
If you look closely at the description of mediatype, then some strange parameter is indicated there. But, there is also a specification:
attribute := token
; Matching of attributes
; is ALWAYS case-insensitive.
value := token / quoted-string
token := 1*<any (US-ASCII) CHAR except SPACE, CTLs, or tspecials>
tspecials := "(" / ")" / "<" / ">" / "#" /
"," / ";" / ":" / "\" / <">
"/" / "[" / "]" / "?" / "="
; Must be in quoted-string,
; to use within parameter values
As can be seen, we can specify any parameter, the main thing is that it meets the requirements presented above!
Therefore, we can embed an additional attribute in the mediatype, which will not affect the image in any way, but the data url will differ from the same image.
Generalized algorithm:
We load the image through a regular request and remove the metadata from created dataUrl from blob.
fetch("https://cdn140.picsart.com/330970106009201.gif").then(async (res) => {
const blob = await res.blob();
const reader = new FileReader();
reader.onload = (ev) => {
// It would be reasonable to remove metadata to the point!
// But for simplicity, I'm using this implementation.
const dataUrl = ev.currentTarget.result.replace(
"data:image/gif;base64",
""
);
};
reader.readAsDataURL(blob);
});
Create/edit img element with src attribute "src=data:image/gif;base64;${Math.random()}${dataUrl}"
That is all!
Example Vanila JS
const url = "https://cdn140.picsart.com/330970106009201.gif";
function loadImage(src) {
fetch(src)
.then((res) => res.blob())
.then(async(blob) => {
const reader = new FileReader();
reader.onload = (ev) => {
const dataUrl = ev.currentTarget.result.replace("data:image/gif;base64", "")
const container = document.getElementById("container");
while (container.firstChild) {
container.firstChild.remove()
}
for (let i = 0; i < 6; i++) {
const img = document.createElement("img");
img.setAttribute("src", `data:image/gif;base64;gif-id=${Date.now()}${dataUrl}`)
container.appendChild(img);
img.addEventListener('click', ev => {
img.setAttribute("src", `data:image/gif;base64;gif-id=${Date.now()}${dataUrl}`)
})
}
};
reader.readAsDataURL(blob);
});
}
loadImage(url);
function updateImage() {
const newSrc = document.getElementById("image-src");
loadImage(document.getElementById("image-src").value);
}
#main {
display: flex;
flex-direction: column;
gap: 5px;
}
img {
width: 128px;
height: 128px;
padding: 5px;
}
<div id="main">
<label>Change gif url if current will become unavailable </label>
<input id="image-src" value="https://cdn140.picsart.com/330970106009201.gif"></input>
<button onclick="updateImage()">Update image source attribute</button>
<span>Click to reset!</span>
<div id="container">
</div>
</div>
Example React
import React, { useState, useRef } from "react";
function App() {
const [stars, setStars] = useState(0);
const [data, setData] = useState(null);
const ref = useRef(null);
React.useEffect(() => {
fetch("https://cdn140.picsart.com/330970106009201.gif")
.then((res) => res.blob())
.then(async (text) => {
const reader = new FileReader();
reader.onload = (ev) => {
setData(ev.currentTarget.result.replace("data:image/gif;base64", ""));
};
reader.readAsDataURL(text);
});
}, []);
return (
<React.Fragment>
<p onClick={() => setStars((s) => s + 1)}>+</p>
{data &&
new Array(stars).fill().map((s, ind) => {
return <Star src={data} key={ind}></Star>;
})}
<p onClick={() => setStars((s) => (s === 0 ? 0 : s - 1))}>-</p>
</React.Fragment>
);
}
export function Star(props) {
const [id] = useState(Math.random());
return (
<img
className="icon"
src={`data:image/gif;base64;gif-id=${id}` + props.src}
alt="animated star"
/>
);
}
export default App;
I came across this thread after searching many others. David Bell's post led me to the solution I needed.
I thought I'd post my experience in the event that it could be useful for anyone trying to accomplish what I was after. This is for an HTML5/JavaScript/jQuery web app that will be an iPhone app via PhoneGap. Testing in Chrome.
The Goal:
When user taps/clicks button A, an animated gif appears and plays.
When user taps/clicks button B, gif disappears.
When user taps/clicks button A again, after tapping/clicking button
B, animated gif should reappear and play from the beginning.
The Problem:
On tap/click of button A, I was appending the gif to an existing div. It would play fine.
Then, on tap/click of button B, I was hiding the container div, then setting the img src of the gif to an empty string (''). Again, no problem (that is, the problem wasn't evident yet.)
Then, on tap/click of button A, after tap/click of button B, I was re-adding the path to the gif as the src.
- This did not work. The gif would show up on subsequent taps/clicks of button A...however, the more I tapped/clicked button A, the more times the gif would load and start over. That is, if I went back and forth, tapping/clicking button A then button B 3 times, the gif would appear and then start/stop/start 3 times...and my whole app started to chug. I guess the gif was being loaded multiple times, even though I had set the src to an empty string when button B was tapped/clicked.
The Solution:
After looking at David Bell's post, I arrived at my answer.
I defined a global variable (let's call it myVar) that held the container div and the image (with the source path) within.
On the tap/click function of button A, I appended that container div to an existing parent div in the dom.
In that function, I created a new variable that holds the src path of the gif.
Just like David suggested, I did this (plus an append):
$('#mainParent').append(myVar);
var imgsrc = $('#my_image').attr('src');
$('#my_image').attr('src', '');
$('#my_image').attr('src', imgsrc);
THEN, in the function for button B, I set the src to an empty string and then removed the div containing the gif:
$('#my_image').attr('src', '');
$('#mainParent').find('#my_image').remove();
Now, I can tap/click button A then button B then button A, etc., all day long. The gif loads and plays on tap/click of button A, then hides on tap/click of button B, then loads and plays from the beginning on subsequent taps of button A every time with no issues.
I worked out a complete solution for this problem. It can be found here: https://stackoverflow.com/a/31093916/1520422
My solution restarts the animation WITHOUT re-loading the image data from the network.
It also enforces the image to repaint to fix some painting artefacts that occured (in chrome).

Categories

Resources