*CTRL-O*

Example Login Animation

index page

This login concept belongs to enszgr. I liked it and I wanted to recreate it with QML. This only focuses on the visual parts of the login screen so don’t expect any functionality.

We have four main components for this animation:

  • A custom check box
  • A custom text box
  • A login message box
  • And a stack to put them all together.

Let’s start with our check box implementation.

Check Box

It’s more of a tick than a check box as it, I’m assuming from the design, doesn’t need any click effects. To achieve the animation in the design we are going to use two Rectangles and states to manage their animation. First, let’s declare some properties that we will need for this.


property int shortLineWidth: 10
property int longLineWidth: 25
property int lineThickness: 5
property bool checked: false
property color color: "#BA68C8"

I thought, since we won’t be handling any click events it makes more sense to individually set the width of the each line in the tick. And using those values I set the width and height of the Item using the formula a^2 + b^2 = c^2 for a triangle.

Item {
    property int shortLineWidth: 10
    property int longLineWidth: 25
    property int lineThickness: 5
    property bool checked: false
    property color color: "#BA68C8"

    id: root
    width: Math.sqrt(Math.pow(shortLineWidth, 2) / 2) + Math.sqrt(Math.pow(longLineWidth, 2) / 2)
    height: Math.sqrt(Math.pow(longLineWidth, 2) / 2)

We have two lines in our check box and the short one has a rotation of 45 degrees and the long one has a rotation of -45 degrees. Using the same triangle formula, we can position the long one based on the short one’s size. Let’s implement our rectangles.

Rectangle {
        id: partOne
        width: 0
        height: lineThickness
        color: root.color
        y: Math.sqrt(Math.pow(shortLineWidth, 2) / 2)
        transform: Rotation {
            origin.x: 0
            origin.y: partOne.height / 2
            angle: 45
        }

        Behavior on width { NumberAnimation { duration: 500; easing.type: Easing.InExpo } }
    }

    Rectangle {
        id: partTwo
        width: 0
        height: lineThickness
        color: root.color
        x: Math.sqrt(Math.pow(partOne.width, 2) / 2) - height / 2
        y: partOne.y + Math.sqrt(Math.pow(partOne.width, 2) / 2)
        transform: Rotation {
            origin.x: 0
            origin.y: partOne.height / 2
            angle: -45
        }

        Behavior on width { NumberAnimation { duration: 500; easing.type: Easing.OutBack } }
    }

The initial widths of the rectangles are 0, since they will be in the unchecked state when first loaded. To control their check state, we can use State and Transition to animate and change their width.

states: [
        State {
            name: "checked"
            when: checked

            PropertyChanges { target: partOne; width: shortLineWidth }
            PropertyChanges { target: partTwo; width: longLineWidth }
        },
        State {
            name: "unchecked"
            when: !checked

            PropertyChanges { target: partOne; width: 0 }
            PropertyChanges { target: partTwo; width: 0 }
        }
    ]

    transitions: [
        Transition {
            from: "unchecked"
            to: "checked"

            SequentialAnimation {
                NumberAnimation { target: partOne; property: "width"; duration: 250; easing.type: Easing.InOutQuad; from: 0; to: shortLineWidth }
                PauseAnimation { duration: 100 }
                NumberAnimation { target: partTwo; property: "width"; duration: 250; easing.type: Easing.OutBack; from: 0; to: longLineWidth }
            }
        },
        Transition {
            from: "checked"
            to: "unchecked"

            SequentialAnimation {
                NumberAnimation { target: partTwo; property: "width"; duration: 250; easing.type: Easing.InOutQuad; from: longLineWidth; to: 0 }
                PauseAnimation { duration: 100 }
                NumberAnimation { target: partOne; property: "width"; duration: 250; easing.type: Easing.InOutQuad; from: shortLineWidth; to: 0 }
            }
        }
    ]

Login Text Box

The text box is a quite simple looking white rectangle with centered text and placeholder text. It also has a vague drop shadow under it. And the check box goes over it. To put the drop shadow under the text box, we use Item as our container and put DropShadow and Rectangle, which contains TextInput, in it. The code is pretty straightforward. When you look at the code DropShadow is over Rectangle because we want to give it a lower z-index by default. We can control this with the z property too.

import QtQuick 2.5
import QtGraphicalEffects 1.0

Item {
    property string placeholder: "ID"
    property bool passwordMaskEnabled: false
    property string passwordMask: "*"

    property alias checked: checkBox.checked
    property alias text: textBox.text
    property alias fontColor: textBox.color
    property alias font: textBox.font

    id: root

    DropShadow {
        anchors.fill: rect
        horizontalOffset: 0
        verticalOffset: 1
        radius: 16.0
        samples: 16
        color: "#424242"
        opacity: 0.2
        source: rect
    }

    Rectangle {
        id: rect
        anchors.fill: parent
        antialiasing: true
        color: "white"

        Text {
            id: placeholderLabel
            anchors.centerIn: parent
            horizontalAlignment: Text.Center
            text: placeholder
            font {
                pixelSize: textBox.font.pixelSize
            }
            opacity: 0.5

            Behavior on opacity { NumberAnimation { duration: 250 } }
        }

        TextInput {
            id: textBox
            width: parent.width - checkBox.width * 2
            height: parent.height
            anchors.centerIn: parent
            clip: true
            focus: root.focus
            font {
                pixelSize: height * 0.5
            }
            verticalAlignment: TextInput.AlignVCenter
            horizontalAlignment: TextInput.AlignHCenter
            onTextChanged: placeholderLabel.opacity = text.length > 0 ? 0 : 0.5;
            echoMode: passwordMaskEnabled ? TextInput.Password : TextInput.Normal
            passwordCharacter: passwordMask
            color: "#34495e"
        }

        LoginCheckBox {
            id: checkBox
            longLineWidth: 15
            shortLineWidth: 6
            lineThickness: 4
            anchors {
                right: parent.right
                rightMargin: checkBox.width * 1
                verticalCenter: parent.verticalCenter
            }
        }
    }
}

Login Stack

This puts together three text boxes and manages the animation.

import QtQuick 2.0

Item {
    id: root

    LoginTextBox {
        id: textBoxID
        width: parent.width / 3
        height: root.height
        z: 2
        anchors {
            left: parent.left
            top: parent.top
        }
        placeholder: qsTr("ID")
        Keys.onReturnPressed: {
            textBoxID.checked = true;
            textBoxPass.anchors.leftMargin = 1;
            textBoxPass.opacity = 1;
            focus = false;
            textBoxPass.forceActiveFocus(Qt.MouseFocusReason);
        }
    }

    LoginTextBox {
        id: textBoxPass
        width: textBoxID.width
        height: root.height
        z: 1
        anchors {
            left: textBoxID.right
            leftMargin: -width * 0.9
            top: parent.top
        }
        opacity: 0.3
        placeholder: qsTr("PASS")
        passwordMaskEnabled: true
        Keys.onReturnPressed: {
            textBoxPass.checked = true;
            loginMessage.anchors.leftMargin = 1;
            loginMessage.opacity = 1;
            loginMessage.text = "Welcome <b>Aristo</b>";
        }

        Behavior on opacity { NumberAnimation { duration: 500; easing.type: Easing.OutQuart } }
        Behavior on anchors.leftMargin { NumberAnimation { duration: 500; easing.type: Easing.OutQuart } }
    }

    LoginMessage {
        id: loginMessage
        width: textBoxID.width
        height: root.height
        z: 0
        anchors {
            left: textBoxPass.right
            leftMargin: -width * 0.9
            top: parent.top
        }
        opacity: 0.3
        fontColor: "#BA68C8"
        font.pixelSize: height * 0.4

        Behavior on opacity { NumberAnimation { duration: 500; easing.type: Easing.OutQuart } }
        Behavior on anchors.leftMargin { NumberAnimation { duration: 500; easing.type: Easing.OutQuart } }
    }
}

You can find the whole source code on my GitHub account.