git @ Cat's Eye Technologies yoob.js / 5992dd2
Begin encapsulating what I wrote in that gist. (Still has bugs.) Chris Pressey 10 years ago
4 changed file(s) with 179 addition(s) and 204 deletion(s). Raw diff Collapse all Expand all
0 <!DOCTYPE html>
1 <head>
2 <meta charset="utf-8">
3 <title>yoob.CanvasResizer Demo</title>
4 <style>
5 html, body, article {
6 width: 100%;
7 margin: 0;
8 padding: 0;
9 line-height: 0;
10 text-align: center;
11 }
12 header {
13 background: goldenrod;
14 }
15 </style>
16 </head>
17 <body>
18 <header>
19 <h1>yoob.Resizer Demo</h1>
20 <p><button id='hide'>Hide header</button> (reload to bring it back)</p>
21 </header>
22 <article>
23 <canvas id="c1"></canvas>
24 </article>
25 </body>
26 <script src="../src/yoob/canvas-resizer.js"></script>
27 <script type="text/javascript">
28 "use strict";
29 var canvas = document.getElementById('c1');
30 var ctx = c1.getContext('2d');
31
32 var cr = (new yoob.CanvasResizer()).init({
33 canvas: canvas,
34 redraw: function() {
35 ctx.fillStyle = 'red';
36 ctx.fillRect(0, 0, canvas.width / 2, canvas.height / 2);
37 ctx.fillStyle = 'blue';
38 ctx.fillRect(canvas.width / 2, canvas.height / 2, canvas.width, canvas.height);
39 },
40 desiredWidth: 640,
41 desiredHeight: 400
42 }).register();
43
44 document.getElementById('hide').onclick = function() {
45 document.getElementsByTagName('header')[0].style.display='none';
46 cr.resizeCanvas();
47 };
48 </script>
+0
-43
eg/resizer-1.html less more
0 <!DOCTYPE html>
1 <head>
2 <meta charset="utf-8">
3 <title>yoob.Resizer Demo 1</title>
4 <style>
5 canvas { border: 1px solid blue; }
6 </style>
7 </head>
8 <body>
9
10 <h1>yoob.Resizer Demo 1</h1>
11
12 <canvas id="c1"></canvas>
13
14 <canvas id="c2"></canvas>
15
16 </body>
17 <script src="../src/yoob/resizer.js"></script>
18 <script type="text/javascript">
19 "use strict";
20 var c1 = document.getElementById('c1');
21 var ctx1 = c1.getContext('2d');
22 var c2 = document.getElementById('c2');
23 var ctx2 = c2.getContext('2d');
24
25 var s1 = (new yoob.Size()).init({ width: 200, height: 400 });
26 s1.applyToElement(c1);
27
28 var s2 = (new yoob.Size()).init({ width: 200, height: 8 });
29 s2.applyToElement(c2);
30
31 var img = new Image();
32 img.onload = function() {
33 var is = (new yoob.Size()).setFromImage(img);
34 is.fit(s1);
35 ctx1.drawImage(img, 0, 0, is.width, is.height);
36 var is = (new yoob.Size()).setFromImage(img);
37 is.fit(s2);
38 ctx2.drawImage(img, 0, 0, is.width, is.height);
39 }
40 img.src = 'charset8.png';
41
42 </script>
0 /*
1 * This file is part of yoob.js version 0.9-PRE
2 * Available from https://github.com/catseye/yoob.js/
3 * This file is in the public domain. See http://unlicense.org/ for details.
4 */
5 if (window.yoob === undefined) yoob = {};
6
7 /*
8 * NOTE: this still has bugs!
9 *
10 * This class provides objects that resize a canvas to fill (or be centered in)
11 * an area in the viewport, with several options.
12 *
13 * See here for the main use cases I wanted to address:
14 * https://gist.github.com/cpressey/0e2d7f8f9a9a28c863ec
15 *
16 * You don't really need this if all you want is a full-viewport canvas;
17 * that's easy enough to do with CSS and a simple onresize handler (see
18 * above article.) But it does accomodate that if you want.
19 */
20 yoob.CanvasResizer = function() {
21 /*
22 * Initializes this CanvasResizer and returns it. Does not hook into
23 * any DOM events, so generally you want to call .register() afterwards.
24 *
25 * `canvas`: the canvas to resize
26 * `redraw`: a function that redraws the canvas after redimensioning
27 * (optional; not needed if redimensionCanvas is false.)
28 * `desired{Width,Height}`: the desired width and height of the canvas
29 * `redimensionCanvas`: should we set the canvas's width and height
30 * properties to the clientWidth and clientHeight of the element
31 * after it has been resized? defaults to true.
32 * `retainAspectRatio`: should we try to retain the aspect ratio
33 * of the canvas after resizing? defaults to true.
34 * `allowExpansion`: should we ever resize the canvas to a size larger
35 * than the desired width & height? defaults to false.
36 * `centerVertically`: should we apply a top margin to the canvas
37 * element, to equal half the available space below it, after resizing
38 * it? defaults to defaults to true.
39 */
40 this.init = function(cfg) {
41 this.canvas = cfg.canvas;
42 this.redraw = cfg.redraw || function() {};
43 this.desiredWidth = cfg.desiredWidth || null;
44 this.desiredHeight = cfg.desiredHeight || null;
45 this.redimensionCanvas = cfg.redimensionCanvas === false ? false : true;
46 this.retainAspectRatio = cfg.retainAspectRatio === false ? false : true;
47 this.allowExpansion = !!cfg.allowExpansion;
48 this.centerVertically = cfg.centerVertically === false ? false : true;
49 return this;
50 };
51
52 this.register = function(w) {
53 var $this = this;
54 var resizeCanvas = function(e) {
55 $this.resizeCanvas(e);
56 };
57 window.addEventListener("load", resizeCanvas);
58 window.addEventListener("resize", resizeCanvas);
59 // TODO: orientationchange?
60 return this;
61 };
62
63 /*
64 * Returns a two-element list, containing the width and height of the
65 * available space in the viewport, measured from the upper-left corner
66 * of the given element.
67 */
68 this.getAvailableSpace = function(elem) {
69 var rect = elem.getBoundingClientRect();
70 var absTop = Math.round(rect.top + window.pageYOffset);
71 var absLeft = Math.round(rect.left + window.pageXOffset);
72 var html = document.documentElement;
73 var availWidth = html.clientWidth - absLeft * 2;
74 var availHeight = html.clientHeight - (absTop + absLeft * 2);
75 return [availWidth, availHeight];
76 };
77
78 /*
79 * Given a destination width and height, return the scaling factor
80 * which is needed to scale the desired width and height to that
81 * destination rectangle.
82 */
83 this.getFitScale = function(destWidth, destHeight) {
84 var widthFactor = this.desiredWidth / destWidth;
85 var heightFactor = this.desiredHeight / destHeight;
86 return 1 / Math.max(widthFactor, heightFactor);
87 };
88
89 this.resizeCanvas = function() {
90 var avail = this.getAvailableSpace(this.canvas.parentElement);
91 var availWidth = avail[0];
92 var availHeight = avail[1];
93 var newWidth = availWidth;
94 var newHeight = availHeight;
95 if (this.preserveAspectRatio) {
96 var scale = this.getFitScale(avail);
97 if (!this.allowExpansion) {
98 scale = Math.min(scale, 1);
99 }
100 newWidth = Math.trunc(this.desiredWidth * scale);
101 newHeight = Math.trunc(this.desiredHeight * scale);
102 } else if (!this.allowExpansion) {
103 // if we don't care about preserving the aspect ratio but do
104 // care about preserving the maximum size, clamp each dimension
105 newWidth = Math.min(newWidth, this.desiredWidth);
106 newHeight = Math.min(newHeight, this.desiredHeight);
107 }
108 if (true) {
109 // TODO: add an option to skip this part...?
110 // you might want to skip it if you have these as %'s
111 this.canvas.style.width = newWidth + "px";
112 this.canvas.style.height = newHeight + "px";
113 }
114 if (this.centerVertically) {
115 this.canvas.style.marginTop = "0";
116 if (availHeight > newHeight) {
117 this.canvas.style.marginTop =
118 Math.trunc((availHeight - newHeight) / 2) + "px";
119 }
120 }
121 if (this.redimensionCanvas) {
122 if (this.canvas.width !== newWidth || this.canvas.height !== newHeight) {
123 this.canvas.width = newWidth;
124 this.canvas.height = newHeight;
125 this.redraw();
126 }
127 }
128 };
129 };
+0
-161
src/yoob/resizer.js less more
0 /*
1 * This file is part of yoob.js version 0.9-PRE
2 * Available from https://github.com/catseye/yoob.js/
3 * This file is in the public domain. See http://unlicense.org/ for details.
4 */
5 if (window.yoob === undefined) yoob = {};
6
7 /*
8 * These classes are provided to help craft solutions to resizing problems,
9 * which (in my experience, anyway) are always rather nasty and ugly.
10 *
11 * First, we have a class which is an abstraction of a size. It may be
12 * used explicitly to specify a desired size, or it may be used to determine
13 * the size of some object (perhaps an Image, or a DOM element, or the
14 * visible portion of a proposed DOM element.)
15 */
16
17 yoob.Size = function() {
18 this.init = function(cfg) {
19 this.width = cfg.width;
20 this.height = cfg.height;
21 return this;
22 };
23
24 this.setFromImage = function(img) {
25 this.width = img.width;
26 this.height = img.height;
27 return this;
28 };
29
30 this.setFromElement = function(elem) {
31 this.width = elem.clientWidth;
32 this.height = elem.clientHeight;
33 return this;
34 };
35
36 this.applyToElement = function(elem) {
37 elem.width = this.width;
38 elem.height = this.height;
39 return this;
40 };
41
42 this.applyAsStyle = function(elem) {
43 elem.style.width = this.width + "px";
44 elem.style.height = this.height + "px";
45 return this;
46 };
47
48 this.getAspectRatio = function() {
49 return this.width / this.height;
50 };
51
52 this.scale = function(factor) {
53 this.width *= factor;
54 this.height *= factor;
55 return this;
56 };
57
58 /*
59 * Return the factor that would be required to scale this size by
60 * in order to make it fit inside the given size, while preserving
61 * the aspect ratio.
62 */
63 this.getFitScale = function(destSize) {
64 var widthFactor = this.width / destSize.width;
65 var heightFactor = this.height / destSize.height;
66 // say this is twice as wide, but 1.5 as high as dest.
67 // then wf will be 2, and hf will be 1.5.
68 // max of these is 2
69 // so fitscale will be 0.5
70 return 1 / Math.max(widthFactor, heightFactor);
71 };
72
73 this.fit = function(destSize) {
74 return this.scale(this.getFitScale(destSize));
75 };
76
77 /*
78 * Assume this size is placed at (left, top) inside the given size.
79 * Truncate this size so that it fits entirely within the given size.
80 * For example, the given size might be window.client{Width,Height}
81 * and the (left, top) might be the position of a div.
82 */
83 this.clip = function(outerSize, left, top) {
84 if (left + this.width > outerSize.width) {
85 this.width = outerSize.width - left;
86 }
87 if (top + this.height > outerSize.height) {
88 this.height = outerSize.height - top;
89 }
90 return this;
91 };
92 };
93
94 /*
95 * Offsets, too.
96 */
97
98 yoob.Offset = function() {
99 this.init = function(cfg) {
100 this.left = cfg.left;
101 this.top = cfg.top;
102 return this;
103 };
104
105 /*
106 * Yep, this does that thing.
107 */
108 this.setFromElement = function(elem) {
109 var left = 0;
110 var top = 0;
111 while (elem) {
112 left += elem.offsetLeft;
113 top += elem.offsetTop;
114 elem = elem.parentElement;
115 }
116 this.left = left;
117 this.top = top;
118 return this;
119 };
120
121 this.applyAsStyle = function(elem) {
122 elem.style.left = this.left + "px";
123 elem.style.top = this.top + "px";
124 return this;
125 };
126 };
127
128 /*
129 * Now that we have Sizes, what we want to do is reconcile them
130 *
131 * So, what we have is a _source size_, which might be the size of an image we
132 * wish to display, or the extents of a playfield we wish to consider.
133 *
134 * We also have a _destination size_, which might be the available screen
135 * real estate for displaying the source image. Or, more precisely, it
136 * might be the available size, in a given DOM element, taking account of
137 * any number of factors, like visibility on the screen.
138 *
139 * From these, and various jiggery-pokery, we compute a _result size_.
140 */
141 yoob.Resizer = function() {
142 this.init = function(cfg) {
143 this.src = cfg.src; // assumed to be a Size
144 this.dest = cfg.dest; // assumed to be a Size
145 return this;
146 };
147
148 this.setSource = function(src) { this.src = src; return this; }
149 this.setDestination = function(dest) { this.dest = dest; return this; }
150
151 /* ??? */
152 /* 3. Profit! */
153
154 /* Things to handle:
155 onload
156 onresize
157 onorientationchange
158 if style.width/height expressed in %, re-get clientW/H after such
159 */
160 };