rematrix
Version:
Matrix transformations made easy
492 lines (431 loc) • 13.8 kB
JavaScript
/*! @license Rematrix v0.4.0
Copyright 2019 Julian Lloyd.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = global || self, factory(global.Rematrix = {}));
}(this, function (exports) { 'use strict';
/**
* @module Rematrix
*/
/**
* Attempts to return a 4x4 matrix describing the CSS transform
* matrix passed in, but will return the identity matrix as a
* fallback.
*
* @param {string} source - `matrix` or `matrix3d` CSS Transform value.
* @return {array}
*/
function fromString(source) {
if (typeof source === 'string') {
var match = source.match(/matrix(3d)?\(([^)]+)\)/);
if (match) {
var raw = match[2].split(', ').map(parseFloat);
return format(raw)
}
}
return identity()
}
/**
* Transformation matrices in the browser come in two flavors:
*
* - `matrix` using 6 values (short)
* - `matrix3d` using 16 values (long)
*
* This utility follows this [conversion guide](https://goo.gl/EJlUQ1)
* to expand short form matrices to their equivalent long form.
*
* @param {array} source - Accepts both short and long form matrices.
* @return {array}
*/
function format(source) {
if (source.constructor !== Array) {
throw new TypeError('Expected array.')
}
if (source.length === 16) {
return source
}
if (source.length === 6) {
var matrix = identity();
matrix[0] = source[0];
matrix[1] = source[1];
matrix[4] = source[2];
matrix[5] = source[3];
matrix[12] = source[4];
matrix[13] = source[5];
return matrix
}
throw new RangeError('Expected array with either 6 or 16 values.')
}
/**
* Returns a matrix representing no transformation. The product of any matrix
* multiplied by the identity matrix will be the original matrix.
*
* > **Tip:** Similar to how `5 * 1 === 5`, where `1` is the identity.
*
* @return {array}
*/
function identity() {
var matrix = [];
for (var i = 0; i < 16; i++) {
i % 5 == 0 ? matrix.push(1) : matrix.push(0);
}
return matrix
}
/**
* Returns a matrix describing the inverse transformation of the source
* matrix. The product of any matrix multiplied by its inverse will be the
* identity matrix.
*
* > **Tip:** Similar to how `5 * (1/5) === 1`, where `1/5` is the inverse.
*
* @param {array} source - Accepts both short and long form matrices.
* @return {array}
*/
function inverse(source) {
var m = format(source);
var s0 = m[0] * m[5] - m[4] * m[1];
var s1 = m[0] * m[6] - m[4] * m[2];
var s2 = m[0] * m[7] - m[4] * m[3];
var s3 = m[1] * m[6] - m[5] * m[2];
var s4 = m[1] * m[7] - m[5] * m[3];
var s5 = m[2] * m[7] - m[6] * m[3];
var c5 = m[10] * m[15] - m[14] * m[11];
var c4 = m[9] * m[15] - m[13] * m[11];
var c3 = m[9] * m[14] - m[13] * m[10];
var c2 = m[8] * m[15] - m[12] * m[11];
var c1 = m[8] * m[14] - m[12] * m[10];
var c0 = m[8] * m[13] - m[12] * m[9];
var determinant = 1 / (s0 * c5 - s1 * c4 + s2 * c3 + s3 * c2 - s4 * c1 + s5 * c0);
if (isNaN(determinant) || determinant === Infinity) {
throw new Error('Inverse determinant attempted to divide by zero.')
}
return [
(m[5] * c5 - m[6] * c4 + m[7] * c3) * determinant,
(-m[1] * c5 + m[2] * c4 - m[3] * c3) * determinant,
(m[13] * s5 - m[14] * s4 + m[15] * s3) * determinant,
(-m[9] * s5 + m[10] * s4 - m[11] * s3) * determinant,
(-m[4] * c5 + m[6] * c2 - m[7] * c1) * determinant,
(m[0] * c5 - m[2] * c2 + m[3] * c1) * determinant,
(-m[12] * s5 + m[14] * s2 - m[15] * s1) * determinant,
(m[8] * s5 - m[10] * s2 + m[11] * s1) * determinant,
(m[4] * c4 - m[5] * c2 + m[7] * c0) * determinant,
(-m[0] * c4 + m[1] * c2 - m[3] * c0) * determinant,
(m[12] * s4 - m[13] * s2 + m[15] * s0) * determinant,
(-m[8] * s4 + m[9] * s2 - m[11] * s0) * determinant,
(-m[4] * c3 + m[5] * c1 - m[6] * c0) * determinant,
(m[0] * c3 - m[1] * c1 + m[2] * c0) * determinant,
(-m[12] * s3 + m[13] * s1 - m[14] * s0) * determinant,
(m[8] * s3 - m[9] * s1 + m[10] * s0) * determinant ]
}
/**
* Returns a 4x4 matrix describing the combined transformations
* of both arguments.
*
* > **Note:** Order is very important. For example, rotating 45°
* along the Z-axis, followed by translating 500 pixels along the
* Y-axis... is not the same as translating 500 pixels along the
* Y-axis, followed by rotating 45° along on the Z-axis.
*
* @param {array} m - Accepts both short and long form matrices.
* @param {array} x - Accepts both short and long form matrices.
* @return {array}
*/
function multiply(m, x) {
var fm = format(m);
var fx = format(x);
var product = [];
for (var i = 0; i < 4; i++) {
var row = [fm[i], fm[i + 4], fm[i + 8], fm[i + 12]];
for (var j = 0; j < 4; j++) {
var k = j * 4;
var col = [fx[k], fx[k + 1], fx[k + 2], fx[k + 3]];
var result = row[0] * col[0] + row[1] * col[1] + row[2] * col[2] + row[3] * col[3];
product[i + k] = result;
}
}
return product
}
/**
* This method has been deprecated!
* See `Rematrix.fromString` instead.
*/
function parse(source) {
console.warn('The `parse` method has been deprecated, please use `fromString`');
return fromString(source)
}
/**
* Returns a 4x4 matrix describing perspective.
*
* @param {number} distance - Measured in pixels.
* @return {array}
*/
function perspective(distance) {
var matrix = identity();
matrix[11] = -1 / distance;
return matrix
}
/**
* Returns a 4x4 matrix describing Z-axis rotation.
*
* > **Tip:** This is just an alias for `Rematrix.rotateZ` for parity with CSS
*
* @param {number} angle - Measured in degrees.
* @return {array}
*/
function rotate(angle) {
return rotateZ(angle)
}
/**
* Returns a 4x4 matrix describing X-axis rotation.
*
* @param {number} angle - Measured in degrees.
* @return {array}
*/
function rotateX(angle) {
var theta = (Math.PI / 180) * angle;
var matrix = identity();
matrix[5] = matrix[10] = Math.cos(theta);
matrix[6] = matrix[9] = Math.sin(theta);
matrix[9] *= -1;
return matrix
}
/**
* Returns a 4x4 matrix describing Y-axis rotation.
*
* @param {number} angle - Measured in degrees.
* @return {array}
*/
function rotateY(angle) {
var theta = (Math.PI / 180) * angle;
var matrix = identity();
matrix[0] = matrix[10] = Math.cos(theta);
matrix[2] = matrix[8] = Math.sin(theta);
matrix[2] *= -1;
return matrix
}
/**
* Returns a 4x4 matrix describing Z-axis rotation.
*
* @param {number} angle - Measured in degrees.
* @return {array}
*/
function rotateZ(angle) {
var theta = (Math.PI / 180) * angle;
var matrix = identity();
matrix[0] = matrix[5] = Math.cos(theta);
matrix[1] = matrix[4] = Math.sin(theta);
matrix[4] *= -1;
return matrix
}
/**
* Returns a 4x4 matrix describing 2D scaling. The first argument
* is used for both X and Y-axis scaling, unless an optional
* second argument is provided to explicitly define Y-axis scaling.
*
* @param {number} scalar - Decimal multiplier.
* @param {number} [scalarY] - Decimal multiplier.
* @return {array}
*/
function scale(scalar, scalarY) {
var matrix = identity();
matrix[0] = scalar;
matrix[5] = typeof scalarY === 'number' ? scalarY : scalar;
return matrix
}
/**
* Returns a 4x4 matrix describing X-axis scaling.
*
* @param {number} scalar - Decimal multiplier.
* @return {array}
*/
function scaleX(scalar) {
var matrix = identity();
matrix[0] = scalar;
return matrix
}
/**
* Returns a 4x4 matrix describing Y-axis scaling.
*
* @param {number} scalar - Decimal multiplier.
* @return {array}
*/
function scaleY(scalar) {
var matrix = identity();
matrix[5] = scalar;
return matrix
}
/**
* Returns a 4x4 matrix describing Z-axis scaling.
*
* @param {number} scalar - Decimal multiplier.
* @return {array}
*/
function scaleZ(scalar) {
var matrix = identity();
matrix[10] = scalar;
return matrix
}
/**
* Returns a 4x4 matrix describing shear. The first argument
* defines X-axis shearing, and an optional second argument
* defines Y-axis shearing.
*
* @param {number} angleX - Measured in degrees.
* @param {number} [angleY] - Measured in degrees.
* @return {array}
*/
function skew(angleX, angleY) {
var thetaX = (Math.PI / 180) * angleX;
var matrix = identity();
matrix[4] = Math.tan(thetaX);
if (angleY) {
var thetaY = (Math.PI / 180) * angleY;
matrix[1] = Math.tan(thetaY);
}
return matrix
}
/**
* Returns a 4x4 matrix describing X-axis shear.
*
* @param {number} angle - Measured in degrees.
* @return {array}
*/
function skewX(angle) {
var theta = (Math.PI / 180) * angle;
var matrix = identity();
matrix[4] = Math.tan(theta);
return matrix
}
/**
* Returns a 4x4 matrix describing Y-axis shear.
*
* @param {number} angle - Measured in degrees
* @return {array}
*/
function skewY(angle) {
var theta = (Math.PI / 180) * angle;
var matrix = identity();
matrix[1] = Math.tan(theta);
return matrix
}
/**
* Returns a CSS Transform property value equivalent to the source matrix.
*
* @param {array} source - Accepts both short and long form matrices.
* @return {string}
*/
function toString(source) {
return ("matrix3d(" + (format(source).join(', ')) + ")")
}
/**
* Returns a 4x4 matrix describing 2D translation. The first
* argument defines X-axis translation, and an optional second
* argument defines Y-axis translation.
*
* @param {number} distanceX - Measured in pixels.
* @param {number} [distanceY] - Measured in pixels.
* @return {array}
*/
function translate(distanceX, distanceY) {
var matrix = identity();
matrix[12] = distanceX;
if (distanceY) {
matrix[13] = distanceY;
}
return matrix
}
/**
* Returns a 4x4 matrix describing 3D translation. The first
* argument defines X-axis translation, the second argument defines Y-axis
* translation, and the third argument defines Z-axis translation.
*
* @param {number} distanceX - Measured in pixels.
* @param {number} distanceY - Measured in pixels.
* @param {number} distanceZ - Measured in pixels.
* @return {array}
*/
function translate3d(distanceX, distanceY, distanceZ) {
var matrix = identity();
if (distanceX && distanceY && distanceZ) {
matrix[12] = distanceX;
matrix[13] = distanceY;
matrix[14] = distanceZ;
}
return matrix
}
/**
* Returns a 4x4 matrix describing X-axis translation.
*
* @param {number} distance - Measured in pixels.
* @return {array}
*/
function translateX(distance) {
var matrix = identity();
matrix[12] = distance;
return matrix
}
/**
* Returns a 4x4 matrix describing Y-axis translation.
*
* @param {number} distance - Measured in pixels.
* @return {array}
*/
function translateY(distance) {
var matrix = identity();
matrix[13] = distance;
return matrix
}
/**
* Returns a 4x4 matrix describing Z-axis translation.
*
* @param {number} distance - Measured in pixels.
* @return {array}
*/
function translateZ(distance) {
var matrix = identity();
matrix[14] = distance;
return matrix
}
exports.format = format;
exports.fromString = fromString;
exports.identity = identity;
exports.inverse = inverse;
exports.multiply = multiply;
exports.parse = parse;
exports.perspective = perspective;
exports.rotate = rotate;
exports.rotateX = rotateX;
exports.rotateY = rotateY;
exports.rotateZ = rotateZ;
exports.scale = scale;
exports.scaleX = scaleX;
exports.scaleY = scaleY;
exports.scaleZ = scaleZ;
exports.skew = skew;
exports.skewX = skewX;
exports.skewY = skewY;
exports.toString = toString;
exports.translate = translate;
exports.translate3d = translate3d;
exports.translateX = translateX;
exports.translateY = translateY;
exports.translateZ = translateZ;
Object.defineProperty(exports, '__esModule', { value: true });
}));