Sometimes you want to render non-pixel things on browser. Like when you are making a word processor which prints text on A4 paper ( 210x297mm )1, or T-shirt authoring tools which prints your photo on L-sized one.
Generally, making authoring tools with HTML elements is a hard thing ( draggable <div>
s, lots of contenteditable
s, and everything is in pixels). But making everything with canvas is another hell. That's where SVG shines.
SVG as an interface ( between screens and prints )
SVG has a great attribute named viewBox
. It represents the size of container ( or say, canvas size ). It can be applied apart from width
or height
, which means that you can do something like "please show this 200x200 image in 50x50" ( the 200x200
part is viewBox
).
Yes, viewBox
can be a "border of two unit systems", like "please show this 200x200 mm image in 50x50 px".
<svg
width="50" // px
height="50" // px
viewBox="0 0 200 200" // Millimeter
>
<rect width="80" height="80" fill="blue" /> // MILLIMETER !!
</svg>
The <rect>
inside, and everything inside this <svg>
now represents millimeter-measured things.
Interconverting pixel and millimeter
Consider dragging the <rect>
inside the <svg>
. Since mouse cursor moves in pixels, you have to convert mouse motion from pixels to millimeters.
Let SVGCanvas
is a React.Component
which renders <svg>
with viewBox
.
class SVGCanvas extends React.Component<Props> {
...
render () {
const { children } = this.props
return <svg width="50" height="50" viewBox="0 0 200 200">{children}</svg>
}
}
Usually, the width
, height
and viewBox
would be computed like...
class SVGCanvas extends React.Component<Props> {
get widthPx () { ... }
get heightPx () { ... }
get widthMm () { ... }
get heightMm () { ... }
get viewBox() {
return `0 0 ${this.widthMm} ${this.heightMm}`
}
render () {
const { children } = this.props
return <svg width={this.widthPx} height={this.heightPx} viewBox={this.viewBox}>{children}</svg>
}
}
Now you know that we can have a currentDpm
(dots per millimeters) or currentDpi
(dots per inch) from the computed values.
It's much simpler to get dpm
, but dpi
is more popular in authoring softwares (like Adobe Photoshop). So we'll get the latter like...
class SVGCanvas extends React.Component<Props> {
...
get currentDpi() {
return this.widthPx / this.widthMm * 25.4
}
render () {
const { children } = this.props
return <svg width={this.widthPx} height={this.heightPx} viewBox={this.viewBox}>{children}</svg>
}
}
Good! Now we have the way to convert pixels to millimeters.
Function px ➔ mm is known as the following ( since 1 inch = 25.4 mm ).
mm = (px / dpi) * 25.4
So you can
// using brand type
// https://basarat.gitbooks.io/typescript/docs/tips/nominalTyping.html
type Pixel = number & { _Pixel: never }
type Millimeter = number & { _Millimeter: never }
const makePx2mm = (dpi: number) => (px: Pixel) => (px / dpi * 25.4) as Millimeter
class SVGCanvas extends React.Component<Props> {
...
get px2mm() {
return makePx2mm(this.currentDpi)
}
render () {
const { children } = this.props
return <svg width={this.widthPx} height={this.heightPx} viewBox={this.viewBox}>{children}</svg>
}
}
I recommend to make px ➔ mm as a "curried" function. Making px2mm
naively will result in a binary function, but you often want to only pass pixel value. So it is better to "bind" dpi
in advance.2
Following to window resize
Now we have px2mm
in our component. But in real world apps, dpi
( or this.widthPx
) might change dynamically. Especially when window resizes.
This is where curried function proves its merits.
class SVGCanvas extends React.Component<Props, State> {
...
componentDidMount() {
document.addEventListener('resize', this.handleResize)
}
handleResize () {
// calculate current width / height to show
this.setState({ widthPx: ..., heightPx: ... })
}
...
}
class SVGCanvas extends React.Component<Props, State> {
get currentDpi() {
// using state.widthPx !!
return this.state.widthPx / this.widthMm * 25.4
}
get px2mm() {
return makePx2mm(this.currentDpi)
}
...
}
Great! Now your px2mm
will change dynamically when browser window resizes.
Conclusion
- SVG has a
viewBox
, which make SVG as an interface between screens and prints. - You can interconvert pixels to millimeter using simple math.
- With currying px ➔ mm converter, it easily follows window resizing.
- Putting them in a component makes your SVG an authoring tool.
Top comments (0)