blob: a992617807cb861867c398df961968c2c25aa659 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// @flow
import { getViewportInfo } from '../env/viewport'
import addPrefix from 'inline-style-prefixer/static/index'
const noUnitsNumberKeys = [
'flex',
'opacity',
'zIndex',
'fontWeight',
'lines'
]
// whether to support using 0.5px to paint 1px width border.
let _supportHairlines: ?boolean
export function supportHairlines () {
if (typeof _supportHairlines === 'undefined') {
const dpr = window.devicePixelRatio
if (dpr && dpr >= 2 && document.documentElement) {
const docElm = document.documentElement
const testElm = document.createElement('div')
const fakeBody = document.createElement('body')
const beforeNode = docElm.firstElementChild || docElm.firstChild
testElm.style.border = '0.5px solid transparent'
fakeBody.appendChild(testElm)
docElm.insertBefore(fakeBody, beforeNode)
_supportHairlines = testElm.offsetHeight === 1
docElm.removeChild(fakeBody)
}
else {
_supportHairlines = false
}
}
return _supportHairlines
}
/**
* remove comments from a cssText.
*/
export function trimComment (cssText: string): string {
return cssText.replace(/(?:\/\*)[\s\S]*?\*\//g, '')
}
let support: boolean | null = null
export function supportSticky (): boolean {
if (support !== null) {
return support
}
const element = window.document.createElement('div')
const elementStyle = element.style
elementStyle.cssText = 'position:-webkit-sticky;position:sticky;'
support = elementStyle.position.indexOf('sticky') !== -1
return support
}
const regPercentage = /^[+-]?\d+(\.\d+)?%$/
export function isPercentage (val: string) {
return regPercentage.test(val)
}
const regUnitsNum = /^([+-]?\d+(?:\.\d+)?)([p,w]x)?$/ // support units: px, wx.
export function normalizeUnitsNum (val: string): string {
const match = val.match(regUnitsNum)
if (!match) { return '' }
let unit = 'px' // px by default.
if (match[2]) {
unit = match[2]
}
return parseScale(parseFloat(match[1]), unit)
}
function getUnitScaleMap () {
const { scale, dpr } = getViewportInfo()
return {
px: scale,
wx: scale * dpr
}
}
function limitScale (val, limit) {
limit = limit || 1
const sign = val === 0 ? 0 : val > 0 ? 1 : -1
let newVal = Math.abs(val) > limit ? val : sign * limit
// support 1px device width.
if (newVal === 1 && val < 1 && supportHairlines()) {
newVal = 0.5
}
return newVal
}
function parseScale (val: number, unit: string): string {
const unitScaleMap = getUnitScaleMap()
return limitScale(val * unitScaleMap[unit]) + 'px'
}
export function normalizeString (styleKey: string, styleVal: string): string {
if (isPercentage(styleVal)) {
return styleVal
}
/**
* 1. test if is a regular scale css. e.g. `width: 100px;`
* this should be a standalone number value with or without unit, otherwise
* it shouldn't be changed.
*/
const unitsNum = normalizeUnitsNum(styleVal)
if (unitsNum) { return unitsNum }
/**
* 2. if a string contains multiple px values, than they should be all normalized.
* values should have wx or px units, otherwise they should be left unchanged.
* e.g.
* transform: translate(10px, 6px, 0)
* border: 2px solid red
*/
const numReg = /([+-]?[\d.]+)([p,w]x)/ig
if (numReg.test(styleVal)) {
const unitScaleMap = getUnitScaleMap()
const val = styleVal.replace(numReg, function (m, $0, $1) {
const res = parseFloat($0) * unitScaleMap[$1]
return limitScale(res) + 'px'
})
return val
}
// otherwise
return styleVal
}
export function autoPrefix (style: {}): {} {
const prefixed = addPrefix(style)
// flex only added WebkitFlex. Should add WebkitBoxFlex also.
const flex = prefixed.flex
if (flex) {
prefixed.WebkitBoxFlex = flex
prefixed.MozBoxFlex = flex
prefixed.MsFlex = flex
}
return prefixed
}
export function normalizeNumber (styleKey: string, styleVal: number): string {
const { scale } = getViewportInfo()
return styleVal * scale + 'px'
}
/**
* normalize style to adapte to current viewport by multiply current scale.
* @param {object} style: should be camelCase.
*/
export function normalizeStyle (style: {}) {
const res = {}
for (const key in style) {
const val = style[key]
if (noUnitsNumberKeys.indexOf(key) > -1) {
res[key] = val
continue
}
switch (typeof val) {
case 'string':
res[key] = normalizeString(key, val)
break
case 'number':
res[key] = normalizeNumber(key, val)
break
default:
res[key] = val
break
}
}
return res
}
/**
* get transformObj
*/
export function getTransformObj (elm: HTMLElement): any {
let styleObj = {}
if (!elm) { return styleObj }
const transformStr = elm.style.webkitTransform
|| elm.style.mozTransform
|| elm.style.transform
if (transformStr && transformStr.match(/(?: *(?:translate|rotate|scale)[^(]*\([^(]+\))+/i)) {
styleObj = transformStr.trim().replace(/, +/g, ',').split(' ').reduce(function (pre, str) {
['translate', 'scale', 'rotate'].forEach(function (name) {
if (new RegExp(name, 'i').test(str)) {
pre[name] = str
}
})
return pre
}, {})
}
return styleObj
}
/**
* translate a transform string from a transformObj.
*/
export function getTransformStr (obj: {}): string {
return Object.keys(obj).reduce(function (pre, key) {
return pre + obj[key] + ' '
}, '')
}
/**
* add transform style to element.
* @param {HTMLElement} elm
* @param {object} style: transform object, format is like this:
* {
* translate: 'translate3d(2px, 2px, 2px)',
* scale: 'scale(0.2)',
* rotate: 'rotate(30deg)'
* }
* @param {boolean} replace: whether to replace all transform properties.
*/
export function addTransform (elm: HTMLElement, style: {}, replace: boolean): void {
if (!style) { return }
let styleObj = {}
if (!replace) {
styleObj = getTransformObj(elm)
}
for (const key in style) {
const val = style[key]
if (val) {
styleObj[key] = val
}
}
const resStr = getTransformStr(styleObj)
elm.style.webkitTransform = resStr
elm.style.mozTransform = resStr
elm.style.transform = resStr
}
/**
* add translate X to the element.
*/
export function addTranslateX (elm: HTMLElement, toAdd: number): void {
if (!toAdd) { return }
const styleObj = getTransformObj(elm)
if (!styleObj.translate) {
styleObj.translate = 'translate3d(0px, 0px, 0px)'
}
styleObj.translate = styleObj.translate.replace(/[+-\d.]+[pw]x/, function ($0) {
return (parseFloat($0) + toAdd) + 'px'
})
const resStr = getTransformStr(styleObj)
elm.style.webkitTransform = resStr
elm.style.mozTransform = resStr
elm.style.transform = resStr
}
/**
* copy a transform behaviour from one element to another.
* key could be: 'translate' | 'scale' | 'rotate'
*/
export function copyTransform (from: HTMLElement, to: HTMLElement, key: string | void): void {
let str
if (!key) {
str = from.style.webkitTransform
|| from.style.mozTransform
|| from.style.transform
}
else {
const fromObj = getTransformObj(from)
if (!fromObj[key]) { return }
const toObj = getTransformObj(to)
toObj[key] = fromObj[key]
str = getTransformStr(toObj)
}
to.style.webkitTransform = str
to.style.mozTransform = str
to.style.transform = str
}
/**
* get color's r, g, b value.
* @param {string} color support all kinds of value of color.
*/
export function getRgb (color: string) {
const haxReg = /#([\da-fA-F]{2})([\da-fA-F]{2})([\da-fA-F]{2})/
const rgbReg = /rgb\((\d+),\s*(\d+),\s*(\d+)\)/
const span = document.createElement('span')
const body = document.body
span.style.cssText = `color: ${color}; width: 0px; height: 0px;`
body && body.appendChild(span)
color = window.getComputedStyle(span).color + ''
body && body.removeChild(span)
let match
match = color.match(haxReg)
if (match) {
return {
r: parseInt(match[1], 16),
g: parseInt(match[2], 16),
b: parseInt(match[3], 16)
}
}
match = color.match(rgbReg)
if (match) {
return {
r: parseInt(match[1]),
g: parseInt(match[2]),
b: parseInt(match[3])
}
}
}
/**
* get style sheet with owner node's id
* @param {string} id owner node id.
*/
export function getStyleSheetById (id?: string) {
if (!id) { return }
const styleSheets = document.styleSheets
const len = styleSheets.length
for (let i = 0; i < len; i++) {
const styleSheet = styleSheets[i]
if (styleSheet.ownerNode.id === id) {
return styleSheet
}
}
}
function getChildrenTotalWidth (children) {
const len = children.length
let total = 0
for (let i = 0; i < len; i++) {
total += children[i].getBoundingClientRect().width
}
return total
}
/**
* get total content width of the element.
* @param {HTMLElement} elm
*/
export function getRangeWidth (elm: HTMLElement) {
const children = elm.children
if (!children) {
return elm.getBoundingClientRect().width
}
if (!Range) {
return getChildrenTotalWidth(children)
}
const range = document.createRange()
if (!range.selectNodeContents) {
return getChildrenTotalWidth(children)
}
range.selectNodeContents(elm)
return range.getBoundingClientRect().width
}