Keeping an images ratio on resize in JavaScript

A little tricky task was given to the team I am working in, to keep an image at its aspect ratio. However the catch is that it needs to be able to go as large as it can to fill the screen at all times, while the user can resize the screen and keeping the images size ratio. Easy you may say, but once you get into it, not so much.

Three of us thought long and hard through many different situation of how we can calculate the size, detect the users actions and calculate the ratio, but with no luck. We also then hunted the internet as all great developers do, which produced many methods using some very long calculation, but none just seemed to work and we knew nothing about the maths.

Then like a shine of light I thought of how you can do it in a simple way. Instead of thinking of the image at 1980px by 1020px and then calculate out the pixel difference etc, you need to think about the image in percentages. When you reduce the images width by say 594 pixel, you would then think of how to calculate the relative pixel difference on the height of the image, but this would be going in the realms of mathematicians. Therefore you stop thinking of that in pixels and think that 1980px reduced by 594px, is a 30% reduction.

Now all you need to do is reduce the height also by 30%, relative to the heights pixels, so here we go…

First we need to find out what the width and heights relative 1% is, so we know where to work from. You need to provide the images original height and width to start this off, then divide each by 100, simple maths.

// Get the one percent of the image width and height
var widthOnePercent = imageOriginWidth / 100;
var heightOnePercent = imageOriginHeight / 100;

Next because we want the image to be as large as it can, we will set the width to be as large as the container to start off the calculations. Once this has been set we can use the images size to find out what percentage the width is at. We know the width of what the image is at in pixels and we know how many pixels are 1%, therefore we can divide the images width by 1%. For example 1386px divided by 19.8px equals 70%.

// Calculate the width percentage
var imageCurrentWidth = $(imageSelector).width();
var imageCurrentPercent = imageCurrentWidth / widthOnePercent;

Now we just need to set the height of the image to 70% of the images height, by multiplying the images 1% height by 70%.

// Calculate the height relative to the percentage of the images width
var imageNewHeight = heightOnePercent * imageCurrentPercent;

However this will work to put the image within the width boundary of the container and the images ratio kept in tact, but potentially due to the width the height could be off the screen. Therefore we need to make sure the image doesn’t go off the screen. This can be calculated out by checking if the height of the image, plus the distance from the top of the window, is not as large as the windows height.

($(window).height() < $(imageSelector).offset().top + imageNewHeight)

Once we have determined this, then we do the same as before but in the opposite direction. This time we start with what height we want the image to be, instead of what height it is. To do this we use the windows height, minus the images top offset, which gives use what space is left.

// Set the new height so it fit on the screen
imageNewHeight = $(window).height() - $(imageSelector).offset().top;

Then the same as before, it is divided by the images 1% height to calculate what the percentage the width is going to be.

// Calculate out what percentage is the height at
imageCurrentPercent = imageNewHeight / heightOnePercent;

// Calculate the width relative to the percentage of the images height
var imageNewWidth = widthOnePercent * imageCurrentPercent;

Below is the full JavaScript code, but to see a full example check out my PureRandom CodePen.

(function($) {

function calIamgeSize(imageSelector, imageOriginWidth, imageOriginHeight) {

// make image as big as it can to start
$(imageSelector).width(
$(imageSelector).parent().width()
);

// Get the one percent of the image width and height
var widthOnePercent = imageOriginWidth / 100;
var heightOnePercent = imageOriginHeight / 100;

// Calculate the width percentage
var imageCurrentWidth = $(imageSelector).width();
var imageCurrentPercent = imageCurrentWidth / widthOnePercent;

// Calculate the height relative to the percentage of the images width
var imageNewHeight = heightOnePercent * imageCurrentPercent;

// If the images height is off the page, then rescale
if ($(window).height() < $(imageSelector).offset().top + imageNewHeight) {

// Set the new height so it fit on the screen
imageNewHeight = $(window).height() - $(imageSelector).offset().top;

// Calculate out what percentage is the height at
imageCurrentPercent = imageNewHeight / heightOnePercent;

// Calculate the width relative to the percentage of the images height
var imageNewWidth = widthOnePercent * imageCurrentPercent;

// Set new width of image
$(imageSelector).width(imageNewWidth);

}

// set new height of image
$(imageSelector).height(imageNewHeight);

}

// Resize image
var imageW = 1000;
var imageH = 667;
var imageClass = ".js-render-image";

$(window).on("resize", function() {

calIamgeSize(imageClass, imageW, imageH);

});

calIamgeSize(imageClass, imageW, imageH);

})($);

JQuery Validate for 3 field date

A difficult and challenging task I had been given is to use the JQuery validate.js to validate the date fields, but this was then split out into 3 fields. One for day, month and of course year, which sounds easy, but it was not. The issue comes that validate.js is built to validate one field on its own and we wanted to validate not just each field, but also as a whole date.

I researched the web and most developers suggested amending the JavaScript library file. However the version we had was minified and of course we had other teams using that copy, so using this method would affect others code. I thought about putting the validation for the whole date in with each of the fields, but this then produced the same validation each message for the whole date 3 times because it was doing it for each of the fields. There was a suggestion from developer to use a group method, but I couldn’t seem to get that working.

So I persisted to make my own method. This may not be the best, but I found it worked well and passed testing so it can’t be that bad. You can read more about the library of JQuery Validate here and learn the basics, which will make this all seem a bit easier.

First we need to validate each field on their own for example make sure the month is between 1 and 12. Instead of making a validation made for each field I made a universal method that all of them could use.

In the example below you can see in the ‘params’ parameter I passed in a array that has the minimal number and the maximum number. You would pass for example of month [1,12]. This is then validated on exit to check the ‘value’ of the field, stored in ‘dateValue’, is between or equal to the range.

$.validator.addMethod("vailddatefield",

function (value, element, params) {

var dateValue = parseInt(value, 10);
var Min = parseInt(params[0], 10);
var Max = parseInt(params[1], 10);
return this.optional(element) || (dateValue >= Min && dateValue <= Max );

},"Please enter a valid age"

);

You can then attach this to the field like so:

//Month validation
$(".month").rules("add", {

vailddatefield: [1,12],
messages: {
vailddatefield:  'Please enter a month value between 1 and 12'
}

});

The issue then comes when you want to validate these all together. If you was to enter the validation for the whole date in the method above then when an error happened it would produce the message once for each field with the same text. This then means we need a single field to evaluate if the full date is valid and then validate against that.

If you add the below snippet into the previous method, you can then pass the class or IDs for each of the date fields. These can then be concatenated together to make a full date, then put into a hidden field. This now holds our full date string in good format ready for validating.

//validate full date
var day = $(params[2]);
var month = $(params[3]);
var year = $(params[4]);
var fullDate= $(params[5]);

$(fullDate).val($(day).val() + '/' + month.val() + '/' + year.val());

This however still doesn’t solve how we are going to validate it. To fire the validation by the library as normal, you need to give focus to the field then leave it, which is impossible because it is hidden. So we use some magic… If you put the below statement in, it forces the JavaScript to validate that field. This then runs its attached validation from the library. If you enter this into the previous method as well, so no matter which fields they start or finish in, it will validate the full date in the end. You will also only get only one message to appear if the field is invalid.

$(fullDate).valid()

Something I also did was stop it validating until all the fields are complete, as you don’t want to say their date is invalid before they even get chance to fill it out, so I changed the above to be the below. This then checks that each field has it minimal length of value before it validates the whole date.

if ((dayBirth.val().length >= 1) && (monthBirth.val().length >= 1) && (yearBirth.val().length > 3)) {

$(fullBirth).valid()          

 }

You can now see the whole code for the validation method. This will validate each field on its own, while also validating the whole date. You will need to add your own method for validating the full date, but if you read up on the library this should be easy.

/// Check Valid date
$.validator.addMethod("vailddatefield",

function (value, element, params) {

var dateValue = parseInt(value, 10);

//Get max and min dates
var Min = parseInt(params[0], 10);
var Max= parseInt(params[1], 10);

//validate full date
var day = $(params[2]);
var month = $(params[3]);
var year = $(params[4]);
var fullDate= $(params[5]);

//enter full date
$(fullDate).val($(day).val() + '/' + month.val() + '/' + year.val());

//Validate full date
if ((dayBirth.val().length >= 1) && (monthBirth.val().length >= 1) && (yearBirth.val().length > 3)) {

$(fullBirth).valid();

}

return this.optional(element) || (dateValue >= Min && dateValue <= Max);

},"Please enter a valid age"

);

Below here you can then see the example of calling the new methods for Month and for the full date. You can always also add more validation methods.

//Month required
$(".validate-month").rules("add", {

vailddatefield: [1,12,'.day','.month','.year','.fulldate'],
messages: {

vailddatefield:  'Please enter a month between 1 and 12'

}

});

//full date
$(".validate-date").rules("add", {

vailddate: '',
messages: {

vailddate:  'Please enter valid date'

}

});

Any thoughts and other methods people have seen can be shared in the comments section below.

ARIA Control JavaScript Library

This is a library to automatically inject the standard for screen readers mark-up ARIA. ARIA is Accessible Rich Internet Applications which is a way to make websites and applications more accessible to people with disabilities.

This libraries purpose is to automatically inject the necessary tags and commands to the users mark-up, so that it meets the standards as much as possible. There are also commands for the users to inject the methods on to specific parts if the library does not get them automatically. Also the automatic functionality can be turned off if only partly needed.

Download the library on GitHub 

View the examples on CodePen

Here are some helpful links about ARIA to help understand the tags and their values.

 

ARIA Control Library V1 Method Breakdown

Hidden (has auto)

This detects all the elements that are ‘display:none’ and/or ‘visibility:hidden’. It will then add the ARIA ‘aria-hidden’ and give it a role of ‘presentation’. This makes the element hidden from screen readers. There is also a method to make the element un hidden if the state changes.

Hidden Message (has auto)

This method uses the given class in the options to make the element display off the page. This means that the screen reader can see the message and read it out, but sighted users won’t be able to see the message. This is useful if you want to give extra information only to the users using a screen reader.

Notifications

This sets the attributes for ‘aria-atomic’, ‘role’ and ‘aria-live’. These are the attributes best served as a notification.

Alert (has auto)

This is for error messages and other alerts to the user. This will use the notification method above with the settings as ‘aria-atomic: true’, ‘role: alert’ and ‘aria-live: rude’. This can be automatically run with a set class.

Warning (has auto)

This is for warning messages and other none critical alerts to the user. This will use the notification method above with the settings as ‘aria-atomic: true’, ‘role: alert’ and ‘aria-live: assertive’. This can be automatically run with a set class.

Message (has auto)

This is for any message that is not critical like a success message. This will use the notification method above with the settings as ‘aria-atomic: true’, ‘role: alert’ and ‘aria-live: polite’. This can be automatically run with a set class.

Required (has auto)

Any element, usually inputs, that has the attribute ‘required’ on them will have the ARIA tag ‘aria-required’ added to the element. This can also be done manually by passing the class.

Popup (has auto)

With this method you need to set up the popup classes for the control and the popup. It is mainly aimed at the tooltip example, e.g. if you click in a field and then a tooltip shows. The method will give the control the aria tags to show it has a popup and the tooltip the tags to show what controls it. You can also pass the role type, but by default it is ‘tooltip’.

Show and Hide Popup

These are more helper methods to the above. When the popup shows or hides you can call these method to update the elements settings.

Checked Elements (has auto)

Both Checkboxes and Radio buttons will automatically have the check item injected with the ARIA ‘aria-checked’. This marks if the element is checked or not, for which the change event is also added to all of the elements so if it becomes checked or unchecked then it is updated.

Disabled (has auto)

Any disabled element will have the ‘aria-disabled’ tag added to it.

Selected Option (has auto)

This will make all the options in a select element ‘aria-selected: false’ then find the selected option to set it to true. It also adds the event change to detect when the selected has changed and updates them.

Max and Min (has auto)

This will mainly be for inputs with a max or min value. It finds if they have a value then puts them in their relative tags, either ‘aria-valuemin’ or ‘aria-valuemax’.

Navigation (only has auto)

A complex one, but easy to set up. With all the correct settings this can add the tags to show up to screen readers as a navigation. It will also tag the links as menu items and the sub menu as well as being a popup. It uses the hover event to detect when the sub menu is being shown.

Button (has auto)

This will detect the all button types and add its role as ‘button’

 

ARIA Control Library V1 Options Breakdown

autoDetect (default = true)

This determines if to automatically tag the elements or to only allow manual running.

alertClass (default = .acAlert)

Used for the auto detection for Alert Notifications.

warningClass (default = .acWarning)

Used for the auto detection for Warning Notifications.

msgClass (default = .acMsg)

Used for the auto detection for Message Notifications.

popupClass (default = .acTooltip)

The class of the popup or, as the example was, the tooltip.

popupCtrlClass (default = .acTooltipCtrl)

The element that triggers the popup or tooltip to show.

popupType (default = tooltip)

The role of the popup.

hiddenMsgClass (default = .acHiddenMsg)

The class of any hidden messages that don’t show to slighted users.

navClass (default = .acNav)

The navigation containers class.

navParentClass (default = .acSubNav)

The top level links class.

navSubNavClass (default = .acSubMenu)

The sub menu class.