Writing in the front

The Omi framework was officially released → Omi-Transform.

Made css3 transform super easy. Made 60 FPS easy.

As the Omi component-based development special effects motion solution, you can quickly and easily support the CSS3 Transform setup in Omi projects. Css3transform has experienced the baptism of a large number of projects. As a mobile Web special effects solution, it is widely used in wechat, hand-Q interest tribes, suntrace, QQ group, QQ nearby and other projects. At the cost of radical modification of DOM attributes, it brings extremely convenient programmability.

You can get a quick look at the cSS3Transform homepage.

You can also use css3Transform declaratively in React:

render() {
    return (
        <Transform
          translateX={100}
          scaleX=0.5} {
          originX=0.5} {>
          <div>You want to move the DOM</div>
        </Transform>)}Copy the code

That’s not the point. It’s the omi-transform.

Master omi-transform in 3 minutes

Install via NPM

npm install omi-transform
Copy the code

use

import { render, WeElement, define } from "omi"
import "omi-transform"

define("my-app".class extends WeElement {
  static observe = true

  install() {
    this.data.rotateZ = 30
    this.linkRef = (e) = > {
      this.animDiv = e
    }
  }

  installed() {
    setInterval((a)= > {
      //slow
      //this.data.rotateZ += 2

      //fast
      this.animDiv.rotateZ += 2
      //sync for update call of any scenario
      this.data.rotateZ = this.animDiv.rotateZ
    }, 16)
  }

  render(props, data) {
    return (
      <css3-transform rotateZ={data.rotateZ} translateX={0} perspective={0} >
        <div ref={this.linkRef}>
          omi-transform
        </div>
      </css3-transform>
    )
  }
})

render(<my-app />, "body")
Copy the code
  • Use the DOM that needs movement<css3-transform></css3-transform>The parcel
  • Tag the DOM that needs to use cSS3TransformrefUsed to manipulate the DOM directly
  • You can use this.refs.animDiv in component functions to read or set the CSS Transform property
  • This.refs. XXX supports “translateX”, “translateY”, “translateZ”, “scaleX”, “scaleY”, “scaleZ”, “rotateX”, “rotateY”, “RotateZ “, “skewX”, “skewY”, “originX”, “originY”, “originZ”,” Perspective “these properties are set and read
  • Perspective represents the distance of a perspective projection

A DOM in a component may be updated during movement due to other logic. It could be a user interaction, it could be a callback to a data return. Therefore, it is important to preserve the state of the DOM before and after the update, otherwise there will be flickering, jumping effects, and other display logic errors.

Can you see that the code above does not Diff during DOM motion? Component does not update? What if the component updates and all motion states are lost? How did Omi solve this problem? The code above provides the answer:

Use this.data.rotatez to synchronize the state of the moving DOM to prevent accidental updates.

To demonstrate

Supported properties

Property Describe
translateX translateX
translateY translateY
translateZ translateZ
scaleX scaleX
scaleY scaleY
scaleZ scaleZ
rotateX rotateX
rotateY rotateY
rotateZ rotateZ
skewX skewX
skewY skewY
originX the basic x point of rotation
originY the basic y point of rotation
originZ the basic z point of rotation
perspective Perspective projection distance

You can get or you can set.

The performance comparison

Because the React version has a diff process, and then the apply diff to DOM process, the state changes don’t replace the entire innerHTML, so it’s still pretty cheap for browsers to render. However, the time of DIff process in JS still needs to be taken by Profiles. If the time is serious, the UI thread will still be stuck without running in Webworker, resulting in lag, animation lag, frame loss, interaction delay, etc. So it’s worth taking a look at the CPU time.

The following data compares omi-Transform and React-Transform, using Chrome Profiles in both ways.

Let’s take a look at the total time comparison:

The react – transform:

Omi – transform:

  • React takes approximately 1686ms of CPU time in 8739 seconds
  • In Omi mode, the CPU consumption is approximately 700ms in 9254ms seconds

Without profiles, we can imagine that React will be slower, because the state change needs to go through the React life cycle. However, it can be seen that the React time is still within the acceptable range, but not unacceptably slow.

The Omi approach takes exactly the same time as traditional native JS. Because the motion process does not perform DOM Diff, directly manipulate DOM!!

Omi self comparison

//slow
this.data.rotateZ += 2
Copy the code
//fast
this.animDiv.rotateZ += 2
this.data.rotateZ = this.animDiv.rotateZ
Copy the code

Compare the execution efficiency of the above two code blocks. Open Google Browser Performance and run for about 10 seconds. Open Summary comparison:

Slow Fast

You can see that both omi methods have very high performance, a lot of idle time in 10 seconds, but FAST is really fast, and scripting takes much less time. But why not? Because the DOM structure is simple, if the DOM structure is more complex, fast’s direct manipulation of the DOM will beat slow’s way! Let’s verify:

Render’s DOM structure has been modified to be complex:

Open Google Chrome Performance run for about 10 seconds and open Summary comparison:

Slow Fast

You can see that Scripting Time has opened up a gap!

Data before and after comparison:

DOM structure Slow Fast
simple
complex

It can be seen that there is no big difference between the two times of Fast and the two times of Slow. So what is the principle of Fast kernel CSS3Transform?

css3transform

The installation

npm install css3transform
Copy the code

API

Transform(domElement, [notPerspective])
Copy the code

By calling the above line of code, To set or read domElement’s “translateX”, “translateY”, “translateZ”, “scaleX”, “scaleY”, “scaleZ”, “rotateX”, “rotateY”, RotateZ, skewX, skewY, originX, originY, originZ!

The road is simple.

Use the pose

Transform(domElement)//or Transform(domElement, true);

//set 
domElement.translateX = 100
domElement.scaleX = 0.5
domElement.originX = 50

//get 
console.log(domElement.translateX)
Copy the code

Traditional CSS3 programming problems

Previously, we used animate. CSS, zepto/jQuery animate method, or Tween.js + CSS3 for interactive effects programming. There are three disadvantages:

  • Is not intuitive
  • Not directly
  • Is not convenient

Is not intuitive

Take a look at the chart below:

The order affects the result, which is not intuitive. So why is that? You can compare the final matrix with new WebKitCSSMatrix(transform_str).

This also directly shows that the matrix does not obey the commutative law. A*B ! = B*A

Not directly

Zepto pose:

$("#some_element").animate({
  opacity: 0.25.left: '50px'.color: '#abcdef'.rotateZ: '45deg'.translate3d: '0, 10 px, 0'
}, 500.'ease-out')
Copy the code

Translate3d: ‘0,10px,0’ Not to mention programming with some library of motion or time. You might argue that ‘ease-out’ doesn’t ease? But if I need x and y and z to correspond to different easing functions, this string-based programming would be a bit tricky. Because it ends up being a string assigned to the DOM element.

Tween. Js posture

var position = { x: 100.y: 100.rotation: 0 },
    target = document.getElementById('target')

    new TWEEN.Tween(position)
    .to({ x: 700.y: 200.rotation: 359 }, 2000)
    .delay(1000)
    .easing(TWEEN.Easing.Elastic.InOut)
    .onUpdate(function update() {
        var t_str= 'translateX(' + position.x + 'px) translateY(' + position.y + 'px) rotate(' + Math.floor(position.rotation) + 'deg)'
        element.style.transform = element.style.msTransform = element.style.OTransform = element.style.MozTransform = element.style.webkitTransform = t_str
    });
Copy the code

Using strings is tiring to watch. Not to mention the torture of writing it.

The animate. CSS position:

@keyframes pulse {
  from {
    -webkit-transform: scale3d(1, 1, 1);
    transform: scale3d(1, 1, 1); 50%} {-webkit-transform: scale3d(1.05, 1.05, 1.05);
    transform: scale3d(1.05, 1.05, 1.05);
  }

  to {
    -webkit-transform: scale3d(1, 1, 1);
    transform: scale3d(1, 1, 1); }}Copy the code

Animate. CSS encapsulates a bunch of keyframe animations, and developers only need to care about adding or removing the relevant animation classes. This gives interactive effects a great deal of traversal, but with a pinch:

  • Not programmable enough
  • Applicable to simple scenarios
  • onlyendCallbacks, nochangeThe callback

Is not convenient

The rotation point reference point of the transform is in the center by default, but sometimes the system is not in the center. We traditionally use transform-Origin to set the reference point.

Note that transform-origin is another property, not transform. But what if you want to sport transform-Origin? Does this design fail? Is there a scene where origin needs to be moved? This is often used in game design and will be discussed in a separate article later. The fact is, there are scenarios that need to move origin to achieve some effect.

summary

Based on the above inconvenience, so there is a CSS3Transform!

  • Css3transform focuses on CSS3 Transform reading and setting an ultra-lightweight JS library, greatly improving the programmability of CSS3 Transform
  • Css3transform is highly abstract and does not bind to any time or motion frame, so it can be easily paired with any time or motion frame
  • Css3transform uses Matrix3D as the final output to dom objects, hardware acceleration without loss of programmability
  • Css3transform has super easy-to-use API, easy to get started in one minute, embedded in real project in two minutes
  • Css3transform extends the power of Transform itself, making Transform Origin more convenient

In actual combat

You can easily create this swing effect with CreateJS ‘Tweenjs:

var element = document.querySelector("#test")
Transform(element)
element.originY = 100
element.skewX = - 20

var Tween = createjs.Tween,
    sineInOutEase = createjs.Ease.sineInOut
Tween.get(element, {loop: true}).to({scaleY: 8.}, 450, sineInOutEase).to({scaleY: 1}, 450, sineInOutEase)
Tween.get(element, {loop: true}).to({skewX: 20}, 900, sineInOutEase).to({skewX: - 20}, 900, sineInOutEase)
Copy the code

The above code is minimal. Here’s a little explanation:

  • The initial skewX for the element is -20, in keeping with scale
  • The originY of the element is 100, with penguin’s Bottom Center as the base point

As you can see, because cSS3Transform is highly abstract, it can be easily paired with Tweenjs without any stress.

The principle of

Css3transform can mix not only CSS3 transforms into DOM elements, but also into any object literal. You can also use css3Transform as a tool, which provides some basic mathematical capabilities.

It is important to note here that the previous positions can continue to be used, but here are three other positions to use.

Grammar 1

Transform(obj, [notPerspective]);
Copy the code

As you can see, nothing else needs to change. However, the first parameter can be passed not only to DOM elements, but also to any object literal, etc.

No pun intended. Look at the posture first

var element = document.querySelector("#test"),
    obj = {}

Transform(obj)

obj.rotateZ = 90

element.style.transform = element.style.msTransform = element.style.OTransform = element.style.MozTransform = element.style.webkitTransform = obj.transform
Copy the code

See, you can pass in not only DOM elements, but also object literals. You can print out obj.transform, which is 90 degrees, so it generates the matrix:

perspective(500px) matrix3d(0.1.0.0.- 1.0.0.0.0.0.1.0.0.0.0.1)
Copy the code

You can also turn off perspective projections, for example:

var element = document.querySelector("#test"),
    obj = {}
// Turn off perspective projection
Transform(obj, true)

obj.rotateZ = 90

element.style.transform = element.style.msTransform = element.style.OTransform = element.style.MozTransform = element.style.webkitTransform = obj.transform
Copy the code

The generated matrix is:

matrix3d(0.1.0.0.- 1.0.0.0.0.0.1.0.0.0.0.1)
Copy the code

What about the posture of movement? Here’s an example with tween.js:

var element = document.querySelector("#test"),
    obj = { translateX: 0.translateY: 0 }

Transform(obj);

var tween = new TWEEN.Tween(obj)
    .to({ translateX: 100.translateY: 100 }, 1000)
    .onUpdate(function () {
        element.style.transform = element.style.msTransform = element.style.OTransform = element.style.MozTransform = element.style.webkitTransform = obj.transform
    })
    .start()

requestAnimationFrame(animate)

function animate(time) {
    requestAnimationFrame(animate)
    TWEEN.update(time)
}
Copy the code

So what about the traditional pose?

var element = document.querySelector("#test")

Transform(element)

var tween = new TWEEN.Tween({ translateX: element.translateX, translateY: element.translateY })
    .to({ translateX: 100.translateY: 100 }, 1000)
    .onUpdate(function () {
        element.translateX = this.translateX
        element.translateY = this.translateY
    })
    .start()

requestAnimationFrame(animate)

function animate(time) {
    requestAnimationFrame(animate)
    TWEEN.update(time)
}
Copy the code

TWEEN. TWEEN iterates through all properties and sets the initial value, as shown in the code inside TWEEN:

 // Set all starting values present on the target object
for (var field in object) {
    _valuesStart[field] = parseFloat(object[field], 10)}Copy the code

So you can’t just say new tween.tween (element). Because before start, the program can completely collect all the attributes that need to be moved. We can encapsulate a Tween ourselves to support this simple approach. Such as:

var Tween = function (obj) {
    this.obj = obj
    return this
}

Tween.prototype = {
    to: function (targets, duration, easing) {
        this.duration = duration
        this.targets = targets
        return this
    },
    start: function () {
        this.startTime = new Date(a)this._beginTick()
    },
    _beginTick: function () {
        var _startValues = {},
            targets = this.targets
        for (var key in targets) {
            if (targets.hasOwnProperty(key)) {
                _startValues[key] = this.obj[key]
            }
        }
        var self  = this
        this._interval = setInterval(function () {
            var dt = new Date() - self.startTime
            for (var key in targets) {
                if (targets.hasOwnProperty(key)) {
                    if (dt >= self.duration) {
                        clearInterval(self._interval)
                    } else {
                        var p = dt / self.duration;
                        var dv = targets[key] - self.obj[key]
                        self.obj[key] += dv * p
                    }
                }
            }
        }, 15)}}Copy the code

To make it easy to use setInterval to loop, you can use another method. Now you can use the following:

var element = document.querySelector("#test")
Transform(element)
var tween = new Tween(element)
    .to({ translateX: 100.translateY: 100 }, 1000)
    .start();
Copy the code

Of course this is a bit off topic. This is just the difference between mounting directly using the DOM versus using a third-party object. Third party hangers contain points to beat the cow on the hill feel. Of course.. It’s not over yet. It’s not just that. You can also use cSS3Transform entirely as a computing tool.

Syntax 2

 Transform.getMatrix3D(option)
Copy the code

posture

var matrix3d = Transform.getMatrix3D({
    translateX: 0.translateY: 100.scaleX:2
})
console.log(matrix3d)
Copy the code

Print it out and you’ll get the following values:

You can do whatever you want with this value. Transform.getmatrix3d: transform.getMatrix3d: transform.getMatrix3d: transform.getMatrix3d

Transform.getMatrix3D = function (option) {
    var defaultOption = {
        translateX: 0.translateY: 0.translateZ: 0.rotateX: 0.rotateY: 0.rotateZ: 0.skewX: 0.skewY: 0.originX: 0.originY: 0.originZ: 0.scaleX: 1.scaleY: 1.scaleZ: 1
    };
    for (var key inoption) { ... . . }Copy the code

Grammar 3

 Transform.getMatrix2D(option)
Copy the code

Not only 3D matrix, cSS3Transform also provides 2D tool function support.

posture

var matrix2d = Transform.getMatrix2D({
    translateX: 0.translateY: 100.scaleX:2
});
console.log(matrix2d);
Copy the code

Print it out and you’ll get the following values:

  • A Horizontal scaling
  • B Horizontal stretching
  • C Vertical stretching
  • D Vertical scaling
  • Tx horizontal displacement
  • Ty vertical displacement

So what’s the use of getting this Matrix2D?

  • Scale: scale(sx, sy) equals matrix(sx, 0, 0, sy, 0, 0);
  • Translate: Translate (tx, ty) equals matrix(1, 0, 0, 1, tx, ty);
  • Rotate (deg) equals matrix(cos(deg), sin(deg), -sin(deg), cos(deg), 0, 0); rotate(deg) equals matrix(cos(deg), sin(deg), -sin(deg), cos(deg), 0, 0);
  • Tension: Skew (Degx, degy) is equal to matrix(1, Tan (deGY), Tan (degx), 1, 0, 0);

Transform.getMatrix2D: Transform. GetMatrix2D: Transform.

Transform.getMatrix2D = function(option){
    var defaultOption = {
        translateX: 0.translateY: 0.rotation: 0.skewX: 0.skewY: 0.originX: 0.originY: 0.scaleX: 1.scaleY: 1}; . . . }Copy the code

Special Attention

Transform.getMatrix2D and transform. getMatrix3D both support origin, Say goodbye to transform-Origin for transform. getMatrix2D and transform. getMatrix3D shew uses half of rotation instead of the traditional Math

For example, 2d skew:

Math.cos(skewY), Math.sin(skewY), -Math.sin(skewX), Math.cos(skewX)
Copy the code

Why do I use half of rotation instead of Math.tan? The reason for this is simple: Math.tan is extremely hard to distort, and there are infinite values that distort across the screen.

Half of rotation doesn’t.

Does getMatrix2D work?

When used with Dom Transformations, it can be used with browsers that do not support CSS3 3D Transforms

For example, we can easily convert some transformation attributes to CSS3 attributes to DOM:

var matrix = Transform.getMatrix2D({
    rotation: 30.scaleX: 0.5.scaleY: 0.5.translateX: 100
});
ele.style.transform = ele.style.msTransform = ele.style.OTransform = ele.style.MozTransform = ele.style.webkitTransform = "matrix(" + [matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty].join(",") + ")"
Copy the code

Used for Canvas and SVG Transformation

What? Canvas and SVG? Yes, for example, draw an image on Canvas rotated 30 degrees, shrunk to 0.5 times, and shifted (200,200) :

var canvas = document.getElementById("ourCanvas"),
    ctx = canvas.getContext("2d"),
    img = new Image(),
    rotation = 30 * Math.PI / 180

img.onload = function () {
    ctx.sava();
    ctx.setTransform(
        0.5 * Math.cos(rotation), 0.5 * Math.sin(rotation),
        0.5 * Math.sin(rotation), 0.5 * Math.cos(rotation),
        200.200
    )
    ctx.drawImage(img, 0.0)
    ctx.restore()
};

img.src = "asset/img/test.png"
Copy the code

This is our traditional pose. After using transform. getMatrix2D, it looks like this:

var canvas = document.getElementById("ourCanvas"),
    ctx = canvas.getContext("2d"),
    img = new Image()

var matrix = Transform.getMatrix2D({
    rotation: 30.scaleX: 0.5.scaleY: 0.5.translateX: 200.translateY: 200
});

img.onload = function () {
    ctx.sava();
    ctx.setTransform(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty);
    ctx.drawImage(img, 0.0);
    ctx.restore();
}

img.src = "asset/img/test.png"
Copy the code

As you can see, this saves developers from having to piece together the matrix themselves. SVG particles will not be used as an example, similar to the DOM example, which you will be able to solve quickly.

Star & Fork

– > omi – transform