Skip to content

Commit

Permalink
Transform visibility when height/width is set. (#6000)
Browse files Browse the repository at this point in the history
* Transform visibility when parent height/width is set.

* width: 0 or height: 0 + transform != 'none' => visible.

* Refactor transform checker functions.
  • Loading branch information
sainthkh authored and jennifer-shehane committed Jan 2, 2020
1 parent 2d9b8e5 commit ed1fa6b
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 27 deletions.
87 changes: 64 additions & 23 deletions packages/driver/src/dom/visibility.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,17 @@ const elHasNoEffectiveWidthOrHeight = ($el) => {
// display:none elements, and generally any elements that are not directly rendered,
// an empty list is returned.

return (elOffsetWidth($el) <= 0) || (elOffsetHeight($el) <= 0) || ($el[0].getClientRects().length <= 0)
// From https://github.com/cypress-io/cypress/issues/5974,
// we learned that when an element has non-'none' transform style value like "translate(0, 0)",
// it is visible even with `height: 0` or `width: 0`.
// That's why we're checking `transform === 'none'` together with elOffsetWidth/Height.

const style = elComputedStyle($el)
const transform = style.getPropertyValue('transform')

return (elOffsetWidth($el) <= 0 && transform === 'none') ||
(elOffsetHeight($el) <= 0 && transform === 'none') ||
($el[0].getClientRects().length <= 0)
}

const elHasNoOffsetWidthOrHeight = ($el) => {
Expand All @@ -123,16 +133,21 @@ const elHasVisibilityHidden = ($el) => {
return $el.css('visibility') === 'hidden'
}

const numberRegex = /-?[0-9]+(?:\.[0-9]+)?/g
const elComputedStyle = ($el) => {
const el = $el[0]

return getComputedStyle(el)
}

const numberRegex = /-?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/g
// This is a simplified version of backface culling.
// https://en.wikipedia.org/wiki/Back-face_culling
//
// We defined view normal vector, (0, 0, -1), - eye to screen.
// and default normal vector, (0, 0, 1)
// When dot product of them are >= 0, item is visible.
const elIsBackface = ($el) => {
const el = $el[0]
const style = getComputedStyle(el)
const style = elComputedStyle($el)
const backface = style.getPropertyValue('backface-visibility')
const backfaceInvisible = backface === 'hidden'
const transform = style.getPropertyValue('transform')
Expand Down Expand Up @@ -163,18 +178,13 @@ const elHasVisibilityCollapse = ($el) => {

// This function checks 2 things that can happen: scale and rotate
const elIsHiddenByTransform = ($el) => {
// We need to see the final calculation of the element.
const el = $el[0]

const style = window.getComputedStyle(el)
const style = elComputedStyle($el)
const transform = style.getPropertyValue('transform')

// Test scaleZ(0)
// width or height of getBoundingClientRect aren't 0 when scaleZ(0).
// But it is invisible.
// Experiment -> https://codepen.io/sainthkh/pen/LYYQGpm
// That's why we're checking transfomation matrix here.
//
if (transform === 'none') {
return false
}

// To understand how this part works,
// you need to understand tranformation matrix first.
// Matrix is hard to explain with only text. So, check these articles.
Expand All @@ -183,26 +193,57 @@ const elIsHiddenByTransform = ($el) => {
// https://en.wikipedia.org/wiki/Rotation_matrix#In_three_dimensions
//
if (transform.startsWith('matrix3d')) {
const m3d = transform.substring(8).match(numberRegex)
const matrix3d = transform.substring(8).match(numberRegex)

// Z Axis values
if (+m3d[2] === 0 && +m3d[6] === 0 && +m3d[10] === 0) {
if (is3DMatrixScaledTo0(matrix3d)) {
return true
}

return isElementOrthogonalWithView(matrix3d)
}

// Other cases
if (transform !== 'none') {
const { width, height } = el.getBoundingClientRect()
const m = transform.match(numberRegex)

if (width === 0 || height === 0) {
return true
}
if (is2DMatrixScaledTo0(m)) {
return true
}

return false
}

const is3DMatrixScaledTo0 = (m3d) => {
const xAxisScaledTo0 = +m3d[0] === 0 && +m3d[4] === 0 && +m3d[8] === 0
const yAxisScaledTo0 = +m3d[1] === 0 && +m3d[5] === 0 && +m3d[9] === 0
const zAxisScaledTo0 = +m3d[2] === 0 && +m3d[6] === 0 && +m3d[10] === 0

if (xAxisScaledTo0 || yAxisScaledTo0 || zAxisScaledTo0) {
return true
}

return false
}

const is2DMatrixScaledTo0 = (m) => {
const xAxisScaledTo0 = +m[0] === 0 && +m[2] === 0
const yAxisScaledTo0 = +m[1] === 0 && +m[3] === 0

if (xAxisScaledTo0 || yAxisScaledTo0) {
return true
}

return false
}

const isElementOrthogonalWithView = (matrix3d) => {
const defaultNormal = [0, 0, -1]
const elNormal = findNormal(matrix3d)
// Simplified dot product.
// [0] and [1] are always 0
const dot = defaultNormal[2] * elNormal[2]

return Math.abs(dot) <= 1e-10
}

const elHasDisplayNone = ($el) => {
return $el.css('display') === 'none'
}
Expand Down
40 changes: 36 additions & 4 deletions packages/driver/test/cypress/integration/dom/visibility_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -801,11 +801,11 @@ describe('src/cypress/dom/visibility', () => {
})

describe('css transform', () => {
describe('element visibility by css transform', () => {
const add = (el) => {
return $(el).appendTo(cy.$$('body'))
}
const add = (el) => {
return $(el).appendTo(cy.$$('body'))
}

describe('element visibility by css transform', () => {
it('is visible when an element is translated a bit', () => {
const el = add(`<div style="transform: translate(10px, 10px)">Translated</div>`)

Expand Down Expand Up @@ -899,6 +899,38 @@ describe('src/cypress/dom/visibility', () => {
})
})

describe('when height/width is set', () => {
it('is visible when transform is not 0, but height is 0', () => {
const el = add('<div style="transform: translate(0, 0); height: 0;">Text</div>')

expect(el).to.be.visible
})

it('is visible when transform is not 0, but width is 0', () => {
const el = add('<p style="transform: rotateX(30deg); width: 0;">Text</p>')

expect(el).to.be.visible
})

it('is visible when parent transform is not 0, but height is 0', () => {
const el = add('<div style="transform: translate(0, 0); height: 0;"><p id="tr-p-0">Text</p></div>')

expect(el.find('#tr-p-0')).to.be.visible
})

it('is visible when parent transform is not 0, but width is 0', () => {
const el = add('<div style="transform: translate(0, 0); height: 0%;"><p id="tr-p-1">Test</p></div>')

expect(el.find('#tr-p-1')).to.be.visible
})

it('is invisible when parent transform is 0, but height is not 0', () => {
const el = add('<div style="transform: scaleX(0); height: 10px"><p id="tr-p-2">Test</p></div>')

expect(el.find('#tr-p-2')).to.be.hidden
})
})

it('is hidden when outside parents transform scale', function () {
expect(this.$parentWithTransformScaleElOutsideScale.find('span')).to.be.hidden
})
Expand Down

4 comments on commit ed1fa6b

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on ed1fa6b Jan 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the linux x64 version of the Test Runner.

You can install this pre-release platform-specific build using instructions at https://on.cypress.io/installing-cypress#Install-pre-release-version.

You will need to use custom CYPRESS_INSTALL_BINARY url and install Cypress using an url instead of the version.

export CYPRESS_INSTALL_BINARY=https://cdn.cypress.io/beta/binary/3.8.2/linux-x64/circle-develop-ed1fa6b4e577166289712abaf240302b3f57fe32-225337/cypress.zip
npm install https://cdn.cypress.io/beta/npm/3.8.2/circle-develop-ed1fa6b4e577166289712abaf240302b3f57fe32-225328/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on ed1fa6b Jan 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppVeyor has built the win32 ia32 version of the Test Runner.

You can install this pre-release platform-specific build using instructions at https://on.cypress.io/installing-cypress#Install-pre-release-version.

You will need to use custom CYPRESS_INSTALL_BINARY url and install Cypress using an url instead of the version.

set CYPRESS_INSTALL_BINARY=https://cdn.cypress.io/beta/binary/3.8.2/win32-ia32/appveyor-develop-ed1fa6b4e577166289712abaf240302b3f57fe32-29858704/cypress.zip
npm install https://cdn.cypress.io/beta/binary/3.8.2/win32-ia32/appveyor-develop-ed1fa6b4e577166289712abaf240302b3f57fe32-29858704/cypress.zip

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on ed1fa6b Jan 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppVeyor has built the win32 x64 version of the Test Runner.

You can install this pre-release platform-specific build using instructions at https://on.cypress.io/installing-cypress#Install-pre-release-version.

You will need to use custom CYPRESS_INSTALL_BINARY url and install Cypress using an url instead of the version.

set CYPRESS_INSTALL_BINARY=https://cdn.cypress.io/beta/binary/3.8.2/win32-x64/appveyor-develop-ed1fa6b4e577166289712abaf240302b3f57fe32-29858704/cypress.zip
npm install https://cdn.cypress.io/beta/binary/3.8.2/win32-x64/appveyor-develop-ed1fa6b4e577166289712abaf240302b3f57fe32-29858704/cypress.zip

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on ed1fa6b Jan 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the darwin x64 version of the Test Runner.

You can install this pre-release platform-specific build using instructions at https://on.cypress.io/installing-cypress#Install-pre-release-version.

You will need to use custom CYPRESS_INSTALL_BINARY url and install Cypress using an url instead of the version.

export CYPRESS_INSTALL_BINARY=https://cdn.cypress.io/beta/binary/3.8.2/darwin-x64/circle-develop-ed1fa6b4e577166289712abaf240302b3f57fe32-225342/cypress.zip
npm install https://cdn.cypress.io/beta/npm/3.8.2/circle-develop-ed1fa6b4e577166289712abaf240302b3f57fe32-225340/cypress.tgz

Please sign in to comment.