Skip to content

Instantly share code, notes, and snippets.

@sukima
Last active January 27, 2019 03:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sukima/9db03da4780f2aac469918539d202398 to your computer and use it in GitHub Desktop.
Save sukima/9db03da4780f2aac469918539d202398 to your computer and use it in GitHub Desktop.
Big Text Dynamic Resize
import Component from '@ember/component';
import { computed } from '@ember/object';
import { htmlSafe } from '@ember/string';
import { scheduleOnce } from '@ember/runloop';
import FontResizer from '../libs/font-resizer';
export default Component.extend({
classNames: ['big-text'],
attributeBindings: ['style'],
style: computed('{fgColor,bgColor}', function() {
let fgColor = this.get('fgColor');
let bgColor = this.get('bgColor');
return htmlSafe(`color: ${fgColor}; background-color: ${bgColor};`);
}),
didInsertElement() {
this._super(...arguments);
this.fontResizer = new FontResizer(
this.element.querySelector('.big-text__wrapper'),
this.element.querySelector('.big-text__content')
);
this.fontResizer.resizeToFit();
},
setFocus(giveFocus) {
let el = this.element.querySelector('.big-text__content');
let method = giveFocus ? 'focus' : 'blur';
el[method]();
},
actions: {
clear() {
this.set('text', '');
this.setFocus(true);
this.send('resizeWhileTyping');
},
resizeWhileTyping() {
scheduleOnce('afterRender', this.fontResizer, 'resizeToFit');
}
}
});
import Component from '@ember/component';
import { or } from '@ember/object/computed';
import { task, timeout } from 'ember-concurrency';
export default Component.extend({
tagName: '',
yieldedFgColor: or('{tempFgColor,fgColor}'),
yieldedBgColor: or('{tempBgColor,bgColor}'),
flashColors: task(function* () {
let setTempColors = () => {
this.set('tempFgColor', this.fgColor);
this.set('tempBgColor', this.bgColor);
};
let setInverseTempColors = () => {
this.set('tempFgColor', this.bgColor);
this.set('tempBgColor', this.fgColor);
};
try {
while (true) {
yield timeout(200);
setInverseTempColors();
yield timeout(200);
setTempColors();
}
} finally {
this.set('tempFgColor', null);
this.set('tempBgColor', null);
}
}).restartable()
});
/* globals Shake */
import Component from '@ember/component';
export default Component.extend({
didInsertElement() {
this._super(...arguments);
this.shakeDetector = new Shake();
this.shakeDetector.start();
window.addEventListener('shake', this, false);
},
willDestroyElement() {
this._super(...arguments);
this.shakeDetector.stop();
window.removeEventListener('shake', this, false);
},
handleEvent(event) {
this.onShake(event);
}
});
import Ember from 'ember';
export default Ember.Controller.extend({
bigTextColor: '#ffffff',
bigTextBgColor: '#000000'
});
import { isBlank } from '@ember/utils';
const MIN_FONT_SIZE = 24;
const MAX_FONT_SIZE = 96;
function propsFor(container) {
return {
get(name) { return container.style.getPropertyValue(name); },
getInt(name) { return parseInt(this.get(name), 10); }
};
}
function adjustorFor(container, functor) {
let props = propsFor(container);
return function(name) {
let unit = props.get(name).replace(/^\d+/, '');
let value = functor(props.getInt(name));
return `${value}${unit}`;
}
}
export default class FontResizer {
constructor(wrapper, content) {
this.wrapper = {
element: wrapper,
style: window.getComputedStyle(wrapper)
};
this.content = {
element: content,
style: window.getComputedStyle(content)
};
}
increaseToFit() {
let failsafe = 10000;
let wrapper = propsFor(this.wrapper);
let content = propsFor(this.content);
let increment = adjustorFor(this.content, v => v + 1);
function isUnderUpperBounds() {
return content.getInt('height') < wrapper.getInt('height');
}
function isBelowMaxFontSize() {
return content.getInt('font-size') < MAX_FONT_SIZE
}
while (isUnderUpperBounds() && isBelowMaxFontSize()) {
if (!--failsafe) throw new Error('Infinate loop');
this.content.element.style.fontSize = increment('font-size');
}
}
decreaseToFit() {
let failsafe = 10000;
let wrapper = propsFor(this.wrapper);
let content = propsFor(this.content);
let decrement = adjustorFor(this.content, v => v - 1);
function fitsWithinBounds() {
return content.getInt('height') < wrapper.getInt('height');
}
function isAboveMinFontSize() {
return content.getInt('font-size') > MIN_FONT_SIZE;
}
while (!fitsWithinBounds() && isAboveMinFontSize()) {
if (!--failsafe) throw new Error('Infinate loop');
this.content.element.style.fontSize = decrement('font-size');
}
}
resizeToFit() {
if (isBlank(this.content.element.innerText)) {
this.content.element.style.fontSize = `${MAX_FONT_SIZE}px`;
return;
}
this.increaseToFit();
this.decreaseToFit();
}
}
.big-text {
position: relative;
}
.big-text__clear-button {
position: absolute;
top: 10px;
right: 20px;
}
.big-text__wrapper {
align-items: center;
border-radius: 6px;
display: flex;
justify-content: center;
min-height: 100vh;
max-height: 100vh;
overflow: hidden;
}
.big-text__content {
font-family: sans-serif;
text-align: center;
font-weight: bold;
overflow: hidden;
overflow-wrap: break-word;
word-wrap: break-word;
}
<div class="container">
<div class="columns">
<PreferencesForm
class="column col-12"
@bigTextColor={{mut this.bigTextColor}}
@bigTextBgColor={{mut this.bigTextBgColor}}
/>
<ColorFlasher
@fgColor={{this.bigTextColor}}
@bgColor={{this.bigTextBgColor}}
as |flasher|
>
<button
class="column col-12 btn btn-default"
{{action flasher.start}}
>
Fake shaking the device
</button>
<ShakeGesture @onShake={{action flasher.start}}/>
<BigText
class="column col-12"
onclick={{action flasher.stop}}
ondblclick={{action flasher.start}}
@fgColor={{flasher.fgColor}}
@bgColor={{flasher.bgColor}}
/>
</ColorFlasher>
</div>
</div>
<button
class="big-text__clear-button btn btn-default"
{{action "clear"}}>
Clear
</button>
<div class="big-text__wrapper">
{{content-editable
class="big-text__content"
allowNewLines=true
autofocus=false
spellcheck=true
disabled=@disabled
value=this.text
placeholder="enter text"
input=(action "resizeWhileTyping")}}
</div>
{{yield (hash
fgColor=this.yieldedFgColor
bgColor=this.yieldedBgColor
start=(perform this.flashColors)
stop=(cancel-all this.flashColors)
)}}
<div class="form-horizontal">
<div class="form-group">
<div class="col-2">
<label for="fg-color" class="form-label">FG:</label>
</div>
<div class="col-10">
{{input
type="color"
id="fg-color"
class="form-input"
value=@bigTextColor}}
</div>
</div>
<div class="form-group">
<div class="col-2">
<label for="bg-color" class="form-label">BG:</label>
</div>
<div class="col-10">
{{input
type="color"
id="bg-color"
class="form-input"
value=@bigTextBgColor}}
</div>
</div>
</div>
{
"version": "0.15.1",
"EmberENV": {
"FEATURES": {}
},
"options": {
"use_pods": false,
"enable-testing": false
},
"dependencies": {
"spectre_css": "https://unpkg.com/spectre.css/dist/spectre.min.css",
"jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js",
"shake.js": "https://alexgibson.github.io/shake.js/shake.js",
"ember": "3.4.3",
"ember-template-compiler": "3.4.3",
"ember-testing": "3.4.3"
},
"addons": {
"ember-concurrency": "0.8.14",
"ember-content-editable": "1.0.3"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment