UNPKG

uikit

Version:

UIkit is a lightweight and modular front-end framework for developing fast and powerful web interfaces.

334 lines (243 loc) • 9.73 kB
import Media from '../mixin/media'; import {getMaxPathLength} from '../core/svg'; import {css, Dimensions, each, isNumber, isString, isUndefined, startsWith, toFloat, toPx, ucfirst} from 'uikit-util'; const props = ['x', 'y', 'bgx', 'bgy', 'rotate', 'scale', 'color', 'backgroundColor', 'borderColor', 'opacity', 'blur', 'hue', 'grayscale', 'invert', 'saturate', 'sepia', 'fopacity', 'stroke']; export default { mixins: [Media], props: props.reduce((props, prop) => { props[prop] = 'list'; return props; }, {}), data: props.reduce((data, prop) => { data[prop] = undefined; return data; }, {}), computed: { props(properties, $el) { return props.reduce((props, prop) => { if (isUndefined(properties[prop])) { return props; } const isColor = prop.match(/color/i); const isCssProp = isColor || prop === 'opacity'; let pos, bgPos, diff; let steps = properties[prop].slice(); if (isCssProp) { css($el, prop, ''); } if (steps.length < 2) { steps.unshift((prop === 'scale' ? 1 : isCssProp ? css($el, prop) : 0) || 0); } const unit = getUnit(steps, prop); if (isColor) { const {color} = $el.style; steps = steps.map(step => parseColor($el, step)); $el.style.color = color; } else if (startsWith(prop, 'bg')) { const attr = prop === 'bgy' ? 'height' : 'width'; steps = steps.map(step => toPx(step, attr, this.$el)); css($el, `background-position-${prop[2]}`, ''); bgPos = css($el, 'backgroundPosition').split(' ')[prop[2] === 'x' ? 0 : 1]; // IE 11 can't read background-position-[x|y] if (this.covers) { const min = Math.min(...steps); const max = Math.max(...steps); const down = steps.indexOf(min) < steps.indexOf(max); diff = max - min; steps = steps.map(step => step - (down ? min : max)); pos = `${down ? -diff : 0}px`; } else { pos = bgPos; } } else { steps = steps.map(toFloat); } if (prop === 'stroke') { if (!steps.some(step => step)) { return props; } const length = getMaxPathLength(this.$el); css($el, 'strokeDasharray', length); if (unit === '%') { steps = steps.map(step => step * length / 100); } steps = steps.reverse(); prop = 'strokeDashoffset'; } props[prop] = {steps, unit, pos, bgPos, diff}; return props; }, {}); }, bgProps() { return ['bgx', 'bgy'].filter(bg => bg in this.props); }, covers(_, $el) { return covers($el); } }, disconnected() { delete this._image; }, update: { read(data) { if (!this.matchMedia) { return; } if (!data.image && this.covers && this.bgProps.length) { const src = css(this.$el, 'backgroundImage').replace(/^none|url\(["']?(.+?)["']?\)$/, '$1'); if (src) { const img = new Image(); img.src = src; data.image = img; if (!img.naturalWidth) { img.onload = () => this.$update(); } } } const {image} = data; if (!image || !image.naturalWidth) { return; } const dimEl = { width: this.$el.offsetWidth, height: this.$el.offsetHeight }; const dimImage = { width: image.naturalWidth, height: image.naturalHeight }; let dim = Dimensions.cover(dimImage, dimEl); this.bgProps.forEach(prop => { const {diff, bgPos, steps} = this.props[prop]; const attr = prop === 'bgy' ? 'height' : 'width'; const span = dim[attr] - dimEl[attr]; if (span < diff) { dimEl[attr] = dim[attr] + diff - span; } else if (span > diff) { const posPercentage = dimEl[attr] / toPx(bgPos, attr, this.$el); if (posPercentage) { this.props[prop].steps = steps.map(step => step - (span - diff) / posPercentage); } } dim = Dimensions.cover(dimImage, dimEl); }); data.dim = dim; }, write({dim}) { if (!this.matchMedia) { css(this.$el, {backgroundSize: '', backgroundRepeat: ''}); return; } dim && css(this.$el, { backgroundSize: `${dim.width}px ${dim.height}px`, backgroundRepeat: 'no-repeat' }); }, events: ['resize'] }, methods: { reset() { each(this.getCss(0), (_, prop) => css(this.$el, prop, '')); }, getCss(percent) { const {props} = this; return Object.keys(props).reduce((css, prop) => { let {steps, unit, pos} = props[prop]; const value = getValue(steps, percent); switch (prop) { // transforms case 'x': case 'y': { unit = unit || 'px'; css.transform += ` translate${ucfirst(prop)}(${ toFloat(value).toFixed(unit === 'px' ? 0 : 2) }${unit})`; break; } case 'rotate': unit = unit || 'deg'; css.transform += ` rotate(${value + unit})`; break; case 'scale': css.transform += ` scale(${value})`; break; // bg image case 'bgy': case 'bgx': css[`background-position-${prop[2]}`] = `calc(${pos} + ${value}px)`; break; // color case 'color': case 'backgroundColor': case 'borderColor': { const [start, end, p] = getStep(steps, percent); css[prop] = `rgba(${ start.map((value, i) => { value = value + p * (end[i] - value); return i === 3 ? toFloat(value) : parseInt(value, 10); }).join(',') })`; break; } // CSS Filter case 'blur': unit = unit || 'px'; css.filter += ` blur(${value + unit})`; break; case 'hue': unit = unit || 'deg'; css.filter += ` hue-rotate(${value + unit})`; break; case 'fopacity': unit = unit || '%'; css.filter += ` opacity(${value + unit})`; break; case 'grayscale': case 'invert': case 'saturate': case 'sepia': unit = unit || '%'; css.filter += ` ${prop}(${value + unit})`; break; default: css[prop] = value; } return css; }, {transform: '', filter: ''}); } } }; function parseColor(el, color) { return css(css(el, 'color', color), 'color') .split(/[(),]/g) .slice(1, -1) .concat(1) .slice(0, 4) .map(toFloat); } function getStep(steps, percent) { const count = steps.length - 1; const index = Math.min(Math.floor(count * percent), count - 1); const step = steps.slice(index, index + 2); step.push(percent === 1 ? 1 : percent % (1 / count) * count); return step; } function getValue(steps, percent, digits = 2) { const [start, end, p] = getStep(steps, percent); return (isNumber(start) ? start + Math.abs(start - end) * p * (start < end ? 1 : -1) : +end ).toFixed(digits); } function getUnit(steps) { return steps.reduce((unit, step) => isString(step) && step.replace(/-|\d/g, '').trim() || unit, ''); } function covers(el) { const {backgroundSize} = el.style; const covers = css(css(el, 'backgroundSize', ''), 'backgroundSize') === 'cover'; el.style.backgroundSize = backgroundSize; return covers; }