Why and How We Toggle the Background
For reasons related to the origin of our site we want to keep our background image although for readability it is less than ideal. So the question was, how do we enable the user to effortlessly hide the background image if they wish to improve the contrast of the page?
As our site uses WooCommerce, for small mobile screens we wanted to have our ‘Contrast’ button in the handheld footer menu.
While the button would say ‘Contrast’, we thought a, not to be missed, tooltip was necessary. Also a tooltip that would, in some way, cope with the absence of hover on touch screens was desirable.
Then it seemed that once the background image was hidden, the new state should apply to all newly opened tabs. This was achieved through the use of a cookie. Indeed, the background image state would apply consistently in the browser until the cookie expired after a day.
How we implemented each of the above goals is described in the following sections.
There’s quite a lot of JavaScript code to follow in this. If you’d like to see the completed code this can be done using the Chrome Developer Tools. Look at the ‘Sources’ tab and find the file ‘https://www.datastream.world/wp-content/uploads/siteground-optimizer-assets/custom-js.min.js’. This is a minified file, however, there is a pretty-print icon ‘{ }’ in the footer of the code window that allows you to see the formatted code. Update 4 May 2021. To look at the particular file you need to be logged in, but no problem, just register and when you’ve finished, there’s a ‘Delete My Account’ option in the Dashboard. We’ll forget all about you!
Steps for a Floating ‘Contrast’ Button
- Guided by how to put your own feedback style twitter badge, however, not intending to use an image in the button, we added the following to the style.css file in our child theme.
#contrast_button {
}
position: fixed;
z-index: 99999;
right: 20px;
top: 100px;
For the button to be mouse sensitive we found we needed a large z-index. The value of 99999 is arbitrary and effective. (Our effort to find the highest z-index in use in the stacking context was disappointingly fruitless.)We also copied header.php to our child theme and included, as a new <div> after #content, the following:
<div id="contrast_button" class="a_contrast">
<a id="a_contrast" href="javascript:void(0);"
title="Toggle background image.">Contrast</a>
</div>
We don’t want the anchor click to take us anywhere, so the href we’ve used is explained in “Is an empty href valid?“. - However, so far the anchor does not look like a button. So we added this style.css in our child theme:
/* make the link look like a button */
#contrast_button{
border: 1px solid black;
border-radius: 5px;
padding: 0px 3px;
background-color: #E3E3E3;
}
#contrast_button:hover{
background-color: #E0FFFF;
}
- When we click the button we want to hide the background image. An inspection of page source or the article How To Properly Add Background Images To Your WordPress Site tells us that we need to remove the css for the class custom-background and replace it with other css that we’ve called custom-contrast. So we added this css to our child theme style.css:
/* Toggled values to replace custom-background */
}
.custom-contrast{
background-image:none;
background-color: #E3E3E3;
Then, following the up-voted code in Changing background color in wordpress with a button so that these values toggle when we click the button, we added the following to our custom.js file (we’re using the theme-customisations plugin):jQuery(function($){
$("#contrast_button").on("click", function(e){
e.preventDefault();
$('body').toggleClass(
'custom-background custom-contrast');
});
});
To allow us to use the $ shortcut we have followed Using ‘$’ instead of ‘jQuery’ in WordPress and also the recommended jQuery syntax.
And that’s all that’s necessary for the floating button.
Handheld Footer Bar ‘Contrast’ Button
Guided by Customize links in the handheld footer bar we added the following to our functions.php:
/*
}
* Add a link in the handheld footer bar
*/
add_filter( 'storefront_handheld_footer_bar_links',
'add_contrast_link' );
function add_contrast_link( $links ) {
$new_links = array(
'contrast' => array(
'priority' => 10,
'callback' => 'contrast_link',
),
);
$links = array_merge( $links, $new_links );
return $links;
}
function contrast_link() {
echo '<a id="contrast_bar_button"
href="javascript:void(0);"
title="Tap to toggle background image.">'
. __( 'Contrast' ) . '</a>';
This is enough to add the link in the footer bar, but we do not yet have an icon. So, additionally we need to add to style.css:.storefront-handheld-footer-bar ul li.contrast > a:before {
content: "f042";
}- For the footer bar button to do the same action as the contrast button, we just need to add its selector to the line:
$("#contrast_button").on("click",function(e){
to become:$("#contrast_button, #contrast_bar_button").on("click", function(e){
- And lastly, we needed to turn off the ‘Contrast’ button in the page body, so we added this to style.css:
/* don't display the contrust button on handheld */
@media only screen and (max-width:768px){
#a_contrast, .a_contrast {
display: none !important;
}
}
And that’s all that’s necessary for the footer bar button.
Adding a Big Tooltip to the ‘Contrast’ Button
- For the tooltip we followed the post Responsive and Mobile-Friendly Tooltip. So firstly we added the attribute
rel="tooltip"
to our anchor elements (id’s a_contrast and contrast_bar_button) above.Then, we wanted the tooltip:
- to automatically disappear after 3 seconds.
- to show itself on page load, so attention is drawn to the button.
- So we included the CSS as shown in the post in style.css, except for small variations as shown here:
#tooltip
Here we’ve chosen a different background colour, we needed to increase the z-index and we choose rounded corners.
{
text-align: center;
color: #fff;
background: #0000FF;
position: absolute;
z-index: 99999;
padding: 15px;
border-radius: 5px;
} - We included the JavaScript largely as shown, but with the following changes for 1a and 1b above and since we already have document ready handling, then c below:
- We added, at the end of the init_tooltip function, a timeout, thus:
window.setTimeout(remove_tooltip, 3000);
- The changes for the on page load tooltip display are fivefold:
- we named the function bound to the variable targets using a function expression, thus:
var show_tip = function()
- So now we have (noting that .bind is, sort of, deprecated), to keep the original functionality:
targets.on( 'mouseenter', show_tip);
- Then we added after the above line of code:
show_tip();
in order to show the tooltip on page load, however, there is a problem, because the object this is the window object and not the button clicked. - So how to select the button? Well the buttons are held in the variable targets and in our case only one is visible, either the floating button or the footer bar button. So which is visible? Guided by How to check an element is visible or not using jQuery this is how we found out:
var vis;
So now we know which button element is visible and thus the button we should tooltip when this is the window object.
$(targets).each(function (index, element){
if ($(element).is(":visible")){
vis = element;
return false;
};
}); - This is how we test if this is the window object, so accordingly we evaluate:
this != null && this === this.window
Hence we replaced:target = $(
this
);
with:target = $(
(this != null && this === this.window)? vis : this
);
- we named the function bound to the variable targets using a function expression, thus:
- We removed the construct:
$( function() { < // code we keep > });
since we already have a document ready construct:jQuery( function($) { // Our toggleClass code });
so we included all our code within this later construct.
- We added, at the end of the init_tooltip function, a timeout, thus:
And that’s all that’s needed for the footer bar ‘contrast’ button.
Using a Cookie to Know the ‘Contrast’ State
- Knowing nothing of the subject, we first read How to Set, Get, and Delete WordPress Cookies (like a Pro). We also read the PHP documentation on setcookie, noting the protocol restrictions regarding HTTP headers.
- Our cookie was generally to have a value of ‘low’ or ‘high’ corresponding to the contrast states of, background image showing, and no background image, respectively. However, we introduced an initial state of ‘init’ which allowed us to show our tooltip for longer when the cookie was created.
- Update 22 May 2020. With the buzz around Chrome’s cookie SameSite policy rollback in the COVID-19 pandemic, we decided now was the time to get SameSite as right as possible. Our current PHP version is 7.3 and is relevant to our solution as discussed. Getting it right was not easy as there are suggestions which did not work for us. On reading Secure better your website with SameSite cookies we used the header function to set the cookie where formerly we had used the setcookie function. So to create our cookie we added the following to theme functions.php:
/*
* Contrast cookie
*/
function set_contrast_cookie() {
// Check if cookie is already set
if(!isset($_COOKIE['contrast_state'])) {
// Set the cookie, expires in 1 day
$expire = date('D, d M Y H:i:s', time() + 60*60*24); // one day from now
header("Set-cookie: contrast_state=init; expires=$expire; path=/; SameSite=Lax");
}
}
add_action('init', 'set_contrast_cookie'); - Once the cookie has been created, all our other cookie handling can be done by the browser with JavaScript.
How should cookies be handled JavaScript? We found How do i set/unset a cookie with jQuery which pointed us to js-cookie.
- Update 4 May 2021. Previously, we did not need to load js-cookie as that was being loaded by WooCommerce. but then we hit a snag when we implemented Perfmatters for performance reasons. With version 1.6.9 of Perfmatters there were ‘Multiple improvements to WooCommerce disable scripts …’, so js-cookie was no longer being loaded on most of our pages. Thus we needed to load it when necessary.
The fact that js-cookie wasn’t being loaded showed up on the console as ‘Cookie’ being undefined. So our approach was to trap this condition with try and catch statements. But now, how does one dynamically load the required script? We came to Dynamically load JS inside JS [duplicate] and way down with only 8 up votes read about extending jquery.getScript to have the loaded script cached. What a nice idea, so we went for that.
- To remove the background image if the cookie value is ‘high’, we need to act as if the ‘contrast’ button had been clicked. So we added the following to custom.js:
var contrast;
// Page load set contrast high if necessary
contrast = Cookies.get("contrast_state");
if (contrast == 'high') {
$('body').toggleClass('custom-background custom-contrast');
}
- Now we needed to set the cookie when the ‘contrast’ button is clicked, so we added to custom.js in the function for the contrast button .on(‘click’:
contrast = Cookies.get("contrast_state");
Cookies.set('contrast_state',
(contrast == 'low') ? 'high' : 'low',
{expires: 1, path: '/', sameSite: 'lax'}); - We decided that we only wanted the tooltip to show without focus being on the ‘contrast’ button on the occasion when the cookie was created. We thought that for it to appear on every page load might be irritating. So we modified the
show_tip();
statement to be:contrast = Cookies.get("contrast_state");
if (contrast == 'init') {
show_tip();
Cookies.set('contrast_state', 'low',
{expires: 1, path: '/', sameSite: 'lax'});
}
- As mentioned above, we wanted the tooltip to show for longer on the initial page load when the cookie is created. It’s now the case that the page load show_tip() is only done when the cookie value is ‘init’, that is only once before the cookie expires. So we made some changes to the custom.js code. In place of :
var show_tip = function()
{
we now have:
target = $((this != null && this === this.window)? vis : this);var show_for;
var is_window;
var show_tip = function()
{
is_window = (this != null && this === this.window);
show_for = is_window ? 6000 : 3000;
target = $(is_window ? vis : this);
And that’s all that’s needed for the cookie to know the contrast state.
Just a reminder, if you want to see all the JavaScript code, we explain how in the last paragraph of the first section ‘Why and How …’.