8 | Turn your image template to images via a simple, self-managed, Netlify-hosted API. 9 |
10 | 11 |12 | 18 |
19 |{props.commandLine && $ }{props.code}
28 |
35 | 8 | For a site deployed to Netlify, use 9 | the Social Image Netlify Build Plugin. 10 | Use case: 11 | Automated social images on Netlify - with Next.js and Resoc 12 | . 13 |
14 | 15 |16 | 22 |
23 | 24 |25 | If you deploy a Eleventy static site to Netlify, use the dedicated Social Image plugin for 11ty. 26 |
27 | 28 |29 | 35 |
36 |
16 | 🖊️ Edit the files in {props.templateDir}. This is just some HTML and CSS, with a little bit
17 | of Mustache, a simple templating system.
18 |
21 | 👀 When you make a change in the template, this page reloads it so you immediately see what 22 | you have just done. 23 |
24 | 25 |
26 | ⚙️ Change the parameter values below to see how your template looks like in various situations.
27 | You can define your own parameters by editing {props.manifestPath}.
28 |
31 | 🚀 When your template is ready, use the command line below to create an image — or a thousand! 32 |
33 |Open a shell and run:
25 |28 | Use case: 29 | Replace ImageMagick with HTML & CSS 30 | 31 |
32 |Install Resoc and Puppeteer:
38 |Create an image:
41 |44 | Use case: 45 | Static, automated social images with NextJS 46 | 47 |
48 |72 | {error} 73 |
74 |117 | innerSpan.style['display'] = 'inline-block'; 118 | innerSpan.innerHTML = originalHTML; 119 | el.innerHTML = ''; 120 | el.appendChild(innerSpan); 121 | } else { 122 | // Reprocessing. 123 | innerSpan = el.querySelector('span.textFitted'); 124 | // Remove vertical align if we're reprocessing. 125 | if (hasClass(innerSpan, 'textFitAlignVert')){ 126 | innerSpan.className = innerSpan.className.replace('textFitAlignVert', ''); 127 | innerSpan.style['height'] = ''; 128 | el.className.replace('textFitAlignVertFlex', ''); 129 | } 130 | } 131 | 132 | // Prepare & set alignment 133 | if (settings.alignHoriz) { 134 | el.style['text-align'] = 'center'; 135 | innerSpan.style['text-align'] = 'center'; 136 | } 137 | 138 | // Check if this string is multiple lines 139 | // Not guaranteed to always work if you use wonky line-heights 140 | var multiLine = settings.multiLine; 141 | if (settings.detectMultiLine && !multiLine && 142 | innerSpan.scrollHeight >= parseInt(window.getComputedStyle(innerSpan)['font-size'], 10) * 2){ 143 | multiLine = true; 144 | } 145 | 146 | // If we're not treating this as a multiline string, don't let it wrap. 147 | if (!multiLine) { 148 | el.style['white-space'] = 'nowrap'; 149 | } 150 | 151 | low = settings.minFontSize; 152 | high = settings.maxFontSize; 153 | 154 | // Binary search for highest best fit 155 | var size = low; 156 | while (low <= high) { 157 | mid = (high + low) >> 1; 158 | innerSpan.style.fontSize = mid + 'px'; 159 | if(innerSpan.scrollWidth <= originalWidth && (settings.widthOnly || innerSpan.scrollHeight <= originalHeight)){ 160 | size = mid; 161 | low = mid + 1; 162 | } else { 163 | high = mid - 1; 164 | } 165 | // await injection point 166 | } 167 | // found, updating font if differs: 168 | if( innerSpan.style.fontSize != size + 'px' ) innerSpan.style.fontSize = size + 'px'; 169 | 170 | // Our height is finalized. If we are aligning vertically, set that up. 171 | if (settings.alignVert) { 172 | addStyleSheet(); 173 | var height = innerSpan.scrollHeight; 174 | if (window.getComputedStyle(el)['position'] === "static"){ 175 | el.style['position'] = 'relative'; 176 | } 177 | if (!hasClass(innerSpan, "textFitAlignVert")){ 178 | innerSpan.className = innerSpan.className + " textFitAlignVert"; 179 | } 180 | innerSpan.style['height'] = height + "px"; 181 | if (settings.alignVertWithFlexbox && !hasClass(el, "textFitAlignVertFlex")) { 182 | el.className = el.className + " textFitAlignVertFlex"; 183 | } 184 | } 185 | } 186 | 187 | // Calculate height without padding. 188 | function innerHeight(el){ 189 | var style = window.getComputedStyle(el, null); 190 | return el.clientHeight - 191 | parseInt(style.getPropertyValue('padding-top'), 10) - 192 | parseInt(style.getPropertyValue('padding-bottom'), 10); 193 | } 194 | 195 | // Calculate width without padding. 196 | function innerWidth(el){ 197 | var style = window.getComputedStyle(el, null); 198 | return el.clientWidth - 199 | parseInt(style.getPropertyValue('padding-left'), 10) - 200 | parseInt(style.getPropertyValue('padding-right'), 10); 201 | } 202 | 203 | //Returns true if it is a DOM element 204 | function isElement(o){ 205 | return ( 206 | typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2 207 | o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName==="string" 208 | ); 209 | } 210 | 211 | function hasClass(element, cls) { 212 | return (' ' + element.className + ' ').indexOf(' ' + cls + ' ') > -1; 213 | } 214 | 215 | // Better than a stylesheet dependency 216 | function addStyleSheet() { 217 | if (document.getElementById("textFitStyleSheet")) return; 218 | var style = [ 219 | ".textFitAlignVert{", 220 | "position: absolute;", 221 | "top: 0; right: 0; bottom: 0; left: 0;", 222 | "margin: auto;", 223 | "display: flex;", 224 | "justify-content: center;", 225 | "flex-direction: column;", 226 | "}", 227 | ".textFitAlignVertFlex{", 228 | "display: flex;", 229 | "}", 230 | ".textFitAlignVertFlex .textFitAlignVert{", 231 | "position: static;", 232 | "}",].join(""); 233 | 234 | var css = document.createElement("style"); 235 | css.type = "text/css"; 236 | css.id = "textFitStyleSheet"; 237 | css.innerHTML = style; 238 | document.body.appendChild(css); 239 | } 240 | })); 241 | -------------------------------------------------------------------------------- /packages/create-img/assets/templates/t1/textFit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * textFit v2.3.1 3 | * Previously known as jQuery.textFit 4 | * 11/2014 by STRML (strml.github.com) 5 | * MIT License 6 | * 7 | * To use: textFit(document.getElementById('target-div'), options); 8 | * 9 | * Will make the *text* content inside a container scale to fit the container 10 | * The container is required to have a set width and height 11 | * Uses binary search to fit text with minimal layout calls. 12 | * Version 2.0 does not use jQuery. 13 | */ 14 | /*global define:true, document:true, window:true, HTMLElement:true*/ 15 | 16 | (function(root, factory) { 17 | "use strict"; 18 | 19 | // UMD shim 20 | if (typeof define === "function" && define.amd) { 21 | // AMD 22 | define([], factory); 23 | } else if (typeof exports === "object") { 24 | // Node/CommonJS 25 | module.exports = factory(); 26 | } else { 27 | // Browser 28 | root.textFit = factory(); 29 | } 30 | 31 | }(typeof global === "object" ? global : this, function () { 32 | "use strict"; 33 | 34 | var defaultSettings = { 35 | alignVert: false, // if true, textFit will align vertically using css tables 36 | alignHoriz: false, // if true, textFit will set text-align: center 37 | multiLine: false, // if true, textFit will not set white-space: no-wrap 38 | detectMultiLine: true, // disable to turn off automatic multi-line sensing 39 | minFontSize: 6, 40 | maxFontSize: 80, 41 | reProcess: true, // if true, textFit will re-process already-fit nodes. Set to 'false' for better performance 42 | widthOnly: false, // if true, textFit will fit text to element width, regardless of text height 43 | alignVertWithFlexbox: false, // if true, textFit will use flexbox for vertical alignment 44 | }; 45 | 46 | return function textFit(els, options) { 47 | 48 | if (!options) options = {}; 49 | 50 | // Extend options. 51 | var settings = {}; 52 | for(var key in defaultSettings){ 53 | if(options.hasOwnProperty(key)){ 54 | settings[key] = options[key]; 55 | } else { 56 | settings[key] = defaultSettings[key]; 57 | } 58 | } 59 | 60 | // Convert jQuery objects into arrays 61 | if (typeof els.toArray === "function") { 62 | els = els.toArray(); 63 | } 64 | 65 | // Support passing a single el 66 | var elType = Object.prototype.toString.call(els); 67 | if (elType !== '[object Array]' && elType !== '[object NodeList]' && 68 | elType !== '[object HTMLCollection]'){ 69 | els = [els]; 70 | } 71 | 72 | // Process each el we've passed. 73 | for(var i = 0; i < els.length; i++){ 74 | processItem(els[i], settings); 75 | } 76 | }; 77 | 78 | /** 79 | * The meat. Given an el, make the text inside it fit its parent. 80 | * @param {DOMElement} el Child el. 81 | * @param {Object} settings Options for fit. 82 | */ 83 | function processItem(el, settings){ 84 | if (!isElement(el) || (!settings.reProcess && el.getAttribute('textFitted'))) { 85 | return false; 86 | } 87 | 88 | // Set textFitted attribute so we know this was processed. 89 | if(!settings.reProcess){ 90 | el.setAttribute('textFitted', 1); 91 | } 92 | 93 | var innerSpan, originalHeight, originalHTML, originalWidth; 94 | var low, mid, high; 95 | 96 | // Get element data. 97 | originalHTML = el.innerHTML; 98 | originalWidth = innerWidth(el); 99 | originalHeight = innerHeight(el); 100 | 101 | // Don't process if we can't find box dimensions 102 | if (!originalWidth || (!settings.widthOnly && !originalHeight)) { 103 | if(!settings.widthOnly) 104 | throw new Error('Set a static height and width on the target element ' + el.outerHTML + 105 | ' before using textFit!'); 106 | else 107 | throw new Error('Set a static width on the target element ' + el.outerHTML + 108 | ' before using textFit!'); 109 | } 110 | 111 | // Add textFitted span inside this container. 112 | if (originalHTML.indexOf('textFitted') === -1) { 113 | innerSpan = document.createElement('span'); 114 | innerSpan.className = 'textFitted'; 115 | // Inline block ensure it takes on the size of its contents, even if they are enclosed 116 | // in other tags like
117 | innerSpan.style['display'] = 'inline-block'; 118 | innerSpan.innerHTML = originalHTML; 119 | el.innerHTML = ''; 120 | el.appendChild(innerSpan); 121 | } else { 122 | // Reprocessing. 123 | innerSpan = el.querySelector('span.textFitted'); 124 | // Remove vertical align if we're reprocessing. 125 | if (hasClass(innerSpan, 'textFitAlignVert')){ 126 | innerSpan.className = innerSpan.className.replace('textFitAlignVert', ''); 127 | innerSpan.style['height'] = ''; 128 | el.className.replace('textFitAlignVertFlex', ''); 129 | } 130 | } 131 | 132 | // Prepare & set alignment 133 | if (settings.alignHoriz) { 134 | el.style['text-align'] = 'center'; 135 | innerSpan.style['text-align'] = 'center'; 136 | } 137 | 138 | // Check if this string is multiple lines 139 | // Not guaranteed to always work if you use wonky line-heights 140 | var multiLine = settings.multiLine; 141 | if (settings.detectMultiLine && !multiLine && 142 | innerSpan.scrollHeight >= parseInt(window.getComputedStyle(innerSpan)['font-size'], 10) * 2){ 143 | multiLine = true; 144 | } 145 | 146 | // If we're not treating this as a multiline string, don't let it wrap. 147 | if (!multiLine) { 148 | el.style['white-space'] = 'nowrap'; 149 | } 150 | 151 | low = settings.minFontSize; 152 | high = settings.maxFontSize; 153 | 154 | // Binary search for highest best fit 155 | var size = low; 156 | while (low <= high) { 157 | mid = (high + low) >> 1; 158 | innerSpan.style.fontSize = mid + 'px'; 159 | if(innerSpan.scrollWidth <= originalWidth && (settings.widthOnly || innerSpan.scrollHeight <= originalHeight)){ 160 | size = mid; 161 | low = mid + 1; 162 | } else { 163 | high = mid - 1; 164 | } 165 | // await injection point 166 | } 167 | // found, updating font if differs: 168 | if( innerSpan.style.fontSize != size + 'px' ) innerSpan.style.fontSize = size + 'px'; 169 | 170 | // Our height is finalized. If we are aligning vertically, set that up. 171 | if (settings.alignVert) { 172 | addStyleSheet(); 173 | var height = innerSpan.scrollHeight; 174 | if (window.getComputedStyle(el)['position'] === "static"){ 175 | el.style['position'] = 'relative'; 176 | } 177 | if (!hasClass(innerSpan, "textFitAlignVert")){ 178 | innerSpan.className = innerSpan.className + " textFitAlignVert"; 179 | } 180 | innerSpan.style['height'] = height + "px"; 181 | if (settings.alignVertWithFlexbox && !hasClass(el, "textFitAlignVertFlex")) { 182 | el.className = el.className + " textFitAlignVertFlex"; 183 | } 184 | } 185 | } 186 | 187 | // Calculate height without padding. 188 | function innerHeight(el){ 189 | var style = window.getComputedStyle(el, null); 190 | return el.clientHeight - 191 | parseInt(style.getPropertyValue('padding-top'), 10) - 192 | parseInt(style.getPropertyValue('padding-bottom'), 10); 193 | } 194 | 195 | // Calculate width without padding. 196 | function innerWidth(el){ 197 | var style = window.getComputedStyle(el, null); 198 | return el.clientWidth - 199 | parseInt(style.getPropertyValue('padding-left'), 10) - 200 | parseInt(style.getPropertyValue('padding-right'), 10); 201 | } 202 | 203 | //Returns true if it is a DOM element 204 | function isElement(o){ 205 | return ( 206 | typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2 207 | o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName==="string" 208 | ); 209 | } 210 | 211 | function hasClass(element, cls) { 212 | return (' ' + element.className + ' ').indexOf(' ' + cls + ' ') > -1; 213 | } 214 | 215 | // Better than a stylesheet dependency 216 | function addStyleSheet() { 217 | if (document.getElementById("textFitStyleSheet")) return; 218 | var style = [ 219 | ".textFitAlignVert{", 220 | "position: absolute;", 221 | "top: 0; right: 0; bottom: 0; left: 0;", 222 | "margin: auto;", 223 | "display: flex;", 224 | "justify-content: center;", 225 | "flex-direction: column;", 226 | "}", 227 | ".textFitAlignVertFlex{", 228 | "display: flex;", 229 | "}", 230 | ".textFitAlignVertFlex .textFitAlignVert{", 231 | "position: static;", 232 | "}",].join(""); 233 | 234 | var css = document.createElement("style"); 235 | css.type = "text/css"; 236 | css.id = "textFitStyleSheet"; 237 | css.innerHTML = style; 238 | document.body.appendChild(css); 239 | } 240 | })); 241 | --------------------------------------------------------------------------------