/material-shapes-svg

An extension of svg.js and svg.filter.js that provides material design shapes for the web

Primary LanguageTypeScriptMIT LicenseMIT

Material Shapes Svg

An implementation of Material Design Shape for the web extending Svg.js and Svg.filter.js

Demo

SVG Button and Rectangle with CutOut

Install

npm install material-shapes-svg

Shapes

Chamfer Rectangle

Creates a rectangle with cut corners.

svgjs.Doc.chamferRect(
width: number,
height: number,
chamferAllLength: number,
): svgjs.MDSChamferRect

svgjs.Doc.chamferRect(
width: number,
height: number,
chamferTopLeftLength: number,
chamferTopRightLength: number,
chamferBottomRightLength: number,
chamferBottomLeftLength: number
): svgjs.MDSChamferRect

Change the chamfer length later after the ChamferRect has been created using the chamfer method

svgjs.MDSChamferRect.chamfer(
width: number,
height: number,
chamferAllLength: number,
): svgjs.MDSChamferRect

svgjs.MDSChamferRect.chamfer(
width: number,
height: number,
chamferTopLeftLength: number,
chamferTopRightLength: number,
chamferBottomRightLength: number,
chamferBottomLeftLength: number
): svgjs.MDSChamferRect

svgjs.MDSChamferRect extends svgjs.Path

<!-- HTML -->
<div class="example-chamfer"></div>
/* CSS: for size example */
.example-chamfer {
    width: 160px;
    height: 40px;
}
import * as svgjs from 'svg.js';
import 'material-shapes-svg';

// Find example div
const div: HTMLElement = document.querySelector('div.example-chamfer');
const width: number = div.offsetWidth;
const height: number = div.offsetHeight;
const chamferLength: number = 8;

// Create container
const container: svgjs.Doc = svgjs(div).size(width, height);

// Draw the chamfer rectangle
container.chamferRect(width, height, chamferLength);

Elevation

Creates a Material Elevation based on the shape.

svgjs.Shape.elevation( z: svgjs.zDepth ): svgjs.Filter

svgjs.Filter.elevate( z: svgjs.zDepth ): svgjs.Filter

<!-- HTML -->
<div class="example-diamond"></div>
/* CSS: Make sure shadows dont get clipped */
.example-diamond .mds-svg {
    overflow: visible;
}

.example-diamond {
    width: 64px;
    height: 64px;
}
import * as svgjs from 'svg.js';
import 'svg.filter.js';
import 'material-shapes-svg';

// find example div
const div: HTMLElement = document.querySelector('div.example-diamond');
const width = element.offsetWidth;
const height = element.offsetHeight;

const container: svgjs.Doc = svgjs(element).size(width, height).addClass('mds-svg');

// Diamond Shape
const diamond: svgjs.Path = container.path(`
    M${width*0.5} 0
    L${width} ${height*0.5}
    L${width*0.5} ${height}
    L0 ${height*0.5}
    Z
`).fill('#fff');

const elevation: svgjs.Filter = diamond.elevation(8);

Ripple

Creates a Material Pressed State aka ripple

svgjs.Shape.ripple( contrast?: svgjs.MDSContrast ): svgjs.MDSRipple

svgjs.MDSRipple.expand( x: number, y: number ): svgjs.MDSRipple

svgjs.MDSRipple.reset(): svgjs.MDSRipple

If you need to update the extents of the ripple use updateMinMax, such as parent container resized.

svgjs.MDSRipple.updateMinMax( width: number, height: number ): svgjs.MDSRipple

svgjs.MDSRipple.contrast: svgjs.MDSContrast ( 'light' | 'dark' )

svgjs.MDSRipple extends svgjs.Circle

<!-- HTML -->
<div class="example-ripple">
    <div class="mds-background"></div>
    <div class="example-text">Click me</div>
    <div class="mds-ripple"></div>
</div>
/* CSS */
.example-ripple {
    width: 160px;
    height: 36px;
    position: relative;
}
.example-ripple .mds-background,
.example-ripple .mds-wash,
.example-ripple .mds-ripple {
    position: absolute;
    overflow: visible;
    top: 0;
    left: 0;
}

.example-ripple .example-text {
    line-height: 36px;
    height: 36px;
    color: #222;
    position: relative;
    text-align: center;
}
import * as svgjs from 'svg.js';
import 'material-shapes-svg';

// Find example div
const div: HTMLElement = document.querySelector('div.example-diamond');
const width = div.offsetWidth;
const height = div.offsetHeight;

// Find background div
const backgroundElement: HTMLElement = <HTMLElement>div.getElementsByClassName('mds-background')[0];

// Find ripple div
const rippleElement: HTMLElement = <HTMLElement>div.getElementsByClassName('mds-ripple')[0];

// Create containers
const backgroundContainer: svgjs.Doc = svgjs(backgroundElement).size(width, height).addClass('mds-svg');
const rippleContainer: svgjs.Doc = svgjs(rippleElement).size(width, height).addClass('mds-svg');

// Draw a shape that can show off mask effect
const chamferRect: svgjs.MDSChamferRect = backgroundContainer.chamferRect(width, height, 10)
.fill('#ccc');
// You need a separate copy of shape for ripple
const rippleRect: svgjs.MDSChamferRect = rippleContainer.chamferRect(width, height, 10)
.fill('#000');

// Add ripple shape
const ripple: svgjs.MDSRipple = rippleRect.ripple('dark');

// Add interactions for demo
div.onmousedown = (event: MouseEvent) => {
    ripple.expand(event.offsetX, event.offsetY);
}
div.onmouseup = () => {
    ripple.reset();
}

Rect Cut Out

Creates cut outs for rectangles. Such as Material App Bottom Bar. See the Inset FAB under the anatomy section.

svgjs.Shape.circleCutOut(
width: number,
height: number,
cutOutSize: number,
alignX: svgjs.CutOutAlignX,
alignY: svgjs.CutOutAlignY,
padding: number,
roundedEdge: number,
showCutOut: boolean
): svgjs.MDSRectCutOut

svgjs.Shape.triangleCutOut(
width: number,
height: number,
cutOutSize: number,
alignX: svgjs.CutOutAlignX,
alignY: svgjs.CutOutAlignY,
padding: number,
showCutOut: boolean
): svgjs.MDSRectCutOut

svgjs.Shape.customCutOut(
width: number, // Width of rect
height: number, // Height of rect
cutOutSize: number, // Size of cutout
customCutOutOpen: string, // Relative svg.path data
customCutOutClosed: string, // Relative svg.path data
alignX: svgjs.CutOutAlignX,
alignY: svgjs.CutOutAlignY,
padding: number,
showCutOut: boolean
): svgjs.MDSRectCutOut

svgjs.MDSRectCutOut.showCutOut( alignX: svgjs.CutOutAlignX ): svgjs.MDSRectCutOut

svgjs.MDSRectCutOut.hideCutOut(): svgjs.MDSRectCutOut

If you need to update the extents of the Rect use resize, such as parent container resized.

svgjs.MDSRectCutOut.resize( width: number,
height: number
): svgjs.MDSRectCutOut

svgjs.MDSRectCutOut.CutOutAlignX: svgjs.CutOutAlignX ( 'start' | 'center' | 'end' )

svgjs.MDSRectCutOut.CutOutAlignY: svgjs.CutOutAlignX ( 'top' | 'bottom' )

svgjs.MDSRectCutOut extends svgjs.Path

<!-- HTML -->
<div class="example-rect-cutout"></div>
/* CSS: for size example */
.example-rect-cutout {
    width: 100%;
    height: 56px;
}
import * as svgjs from 'svg.js';
import 'material-shapes-svg';

// Find example div
const div: HTMLElement = document.querySelector('div.example-rect-cutout');
const width: number = div.offsetWidth;
const height: number = div.offsetHeight;
const padding: number = 16;
const roundedEdge: number = 4;
const fabDiameter: number = 56 + 16; // fab plus cutout padding
const cutOutAlignX = 'end';
const cutOutAlignY = 'top';
const showCutOut = true;

// Create container
const container: svgjs.Doc = svgjs(div).size(width, height);

// Draw the rectangle with circular cutout
const rectCutOut: svgjs.MDSRectCutOut = container.circleCutOut(
    width,
    height,
    fabDiameter,
    cutOutAlignX,
    cutOutAlignY,
    padding,
    roundedEdge,
    showCutOut
);

For custom shapes use relative path data

const cutOutSize = 80;

const radius = `28 28`
const concave = `0 0 0`

const openCutOut = `
a ${radius} ${concave} 14 21
a ${radius} ${concave} 52 0
a ${radius} ${concave} 14 -21
l 0 0`;

const closedCutOut = `
a 23 0 ${concave} 0 0
a 24 0 ${concave} 0 0
a 23 0 ${concave} 0 0
l 0 0`;

const rectCutOut: svgjs.MDSRectCutOut = container.customCutOut(
    width,
    height,
    cutOutSize,
    openCutOut,
    closedCutOut,
    cutOutAlignX,
    cutOutAlignY,
    padding,
    showCutOut
)

RoadMap

Custom Chamfer Shapes supply a shape to cutout of chamfered corners

Rect CutOut refactor circle and triangle to use similar method to custom.

Demo Site more examples