Modern HTML5 Techniques for changing classes
Modern browsers have added classList which provides methods to make it easier to manipulate classes without needing a library:
document.getElementById("MyElement").classList.add('MyClass');
document.getElementById("MyElement").classList.remove('MyClass');
if ( document.getElementById("MyElement").classList.contains('MyClass') )
document.getElementById("MyElement").classList.toggle('MyClass');
Unfortunately, these do not work in Internet Explorer prior to v10, though there is a shim to add support for it to IE8 and IE9, available from this page. It is, though, getting more and more supported.
Simple cross-browser solution
The standard JavaScript way to select an element is using document.getElementById("Id")
, which is what the following examples use - you can of course obtain elements in other ways, and in the right situation may simply use this
instead - however, going into detail on this is beyond the scope of the answer.
To change all classes for an element:
To replace all existing classes with one or more new classes, set the className attribute:
document.getElementById("MyElement").className = "MyClass";
(You can use a space-delimited list to apply multiple classes.)
To add an additional class to an element:
To add a class to an element, without removing/affecting existing values, append a space and the new classname, like so:
document.getElementById("MyElement").className += " MyClass";
To remove a class from an element:
To remove a single class to an element, without affecting other potential classes, a simple regex replace is required:
document.getElementById("MyElement").className =
document.getElementById("MyElement").className.replace
( /(?:^|\s)MyClass(?!\S)/g , '' )
/* Code wrapped for readability - above is all one statement */
An explanation of this regex is as follows:
(?:^|\s) # Match the start of the string or any single whitespace character
MyClass # The literal text for the classname to remove
(?!\S) # Negative lookahead to verify the above is the whole classname
# Ensures there is no non-space character following
# (i.e. must be the end of the string or space)
The g
flag tells the replace to repeat as required, in case the class name has been added multiple times.
To check if a class is already applied to an element:
The same regex used above for removing a class can also be used as a check as to whether a particular class exists:
if ( document.getElementById("MyElement").className.match(/(?:^|\s)MyClass(?!\S)/) )
### Assigning these actions to onclick events:
Whilst it is possible to write JavaScript directly inside the HTML event attributes (such as onclick="this.className+=' MyClass'"
) this is not recommended behaviour. Especially on larger applications, more maintainable code is achieved by separating HTML markup from JavaScript interaction logic.
The first step to achieving this is by creating a function, and calling the function in the onclick attribute, for example:
<script type="text/javascript">
function changeClass(){
// Code examples from above
}
</script>
...
<button onclick="changeClass()">My Button</button>
(It is not required to have this code in script tags, this is simply for the brevity of example, and including the JavaScript in a distinct file may be more appropriate.)
The second step is to move the onclick event out of the HTML and into JavaScript, for example using addEventListener
<script type="text/javascript">
function changeClass(){
// Code examples from above
}
window.onload = function(){
document.getElementById("MyElement").addEventListener( 'click', changeClass);
}
</script>
...
<button id="MyElement">My Button</button>
(Note that the window.onload part is required so that the contents of that function are executed after the HTML has finished loading - without this, the MyElement might not exist when the JavaScript code is called, so that line would fail.)
JavaScript Frameworks and Libraries
The above code is all in standard JavaScript, however, it is common practice to use either a framework or a library to simplify common tasks, as well as benefit from fixed bugs and edge cases that you might not think of when writing your code.
Whilst some people consider it overkill to add a ~50 KB framework for simply changing a class, if you are doing any substantial amount of JavaScript work or anything that might have unusual cross-browser behavior, it is well worth considering.
(Very roughly, a library is a set of tools designed for a specific task, whilst a framework generally contains multiple libraries and performs a complete set of duties.)
The examples above have been reproduced below using jQuery, probably the most commonly used JavaScript library (though there are others worth investigating too).
(Note that $
here is the jQuery object.)
Changing Classes with jQuery:
$('#MyElement').addClass('MyClass');
$('#MyElement').removeClass('MyClass');
if ( $('#MyElement').hasClass('MyClass') )
In addition, jQuery provides a shortcut for adding a class if it doesn't apply, or removing a class that does:
$('#MyElement').toggleClass('MyClass');
### Assigning a function to a click event with jQuery:
$('#MyElement').click(changeClass);
or, without needing an id:
$(':button:contains(My Button)').click(changeClass);
Best Answer
Cause
Some images are just very hard to down-sample and interpolate such as this one with curves when you want to go from a large size to a small.
Browsers appear to typically use bi-linear (2x2 sampling) interpolation with the canvas element rather than bi-cubic (4x4 sampling) for (likely) performance reasons.
If the step is too huge then there are simply not enough pixels to sample from which is reflected in the result.
From a signal/DSP perspective you could see this as a low-pass filter's threshold value set too high, which may result in aliasing if there are many high frequencies (details) in the signal.
Solution
Update 2018:
Here's a neat trick you can use for browsers which supports the
filter
property on the 2D context. This pre-blurs the image which is in essence the same as a resampling, then scales down. This allows for large steps but only needs two steps and two draws.Pre-blur using number of steps (original size / destination size / 2) as radius (you may need to adjust this heuristically based on browser and odd/even steps - here only shown simplified):
Support for filter as ogf Oct/2018:
Update 2017: There is now a new property defined in the specs for setting resampling quality:
It's currently only supported in Chrome. The actual methods used per level is left to the vendor to decide, but it's reasonable to assume Lanczos for "high" or something equivalent in quality. This means step-down may be skipped altogether, or larger steps can be used with fewer redraws, depending on the image size and
Support for
imageSmoothingQuality
:browser. Until then..:
End of transmission
The solution is to use step-down to get a proper result. Step-down means you reduce the size in steps to allow the limited interpolation range to cover enough pixels for sampling.
This will allow good results also with bi-linear interpolation (it actually behaves much like bi-cubic when doing this) and the overhead is minimal as there are less pixels to sample in each step.
The ideal step is to go to half the resolution in each step until you would set the target size (thanks to Joe Mabel for mentioning this!).
Modified fiddle
Using direct scaling as in original question:
Using step-down as shown below:
In this case you will need to step down in 3 steps:
In step 1 we reduce the image to half by using an off-screen canvas:
Step 2 reuses the off-screen canvas and draws the image reduced to half again:
And we draw once more to main canvas, again reduced
to halfbut to the final size:Tip:
You can calculate total number of steps needed, using this formula (it includes the final step to set target size):