Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,17 @@ Motion adheres to [Semantic Versioning](http://semver.org/).

Undocumented APIs should be considered internal and may change without warning.

## [12.34.0] 2026-02-09
## [12.34.1] 2026-02-17

### Fixed

- `useScroll`: Ensure animations aren't hardware accelerated when `target` is set.
- Improve animatable `"none"` generation for mask values.

## [12.34.0] 2026-02-09

### Added

- `useScroll`: Hardware accelerated animations.

## [12.33.2] 2026-02-06
Expand Down
8 changes: 4 additions & 4 deletions dev/html/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "html-env",
"private": true,
"version": "12.34.0",
"version": "12.34.1",
"type": "module",
"scripts": {
"dev": "vite",
Expand All @@ -10,9 +10,9 @@
"preview": "vite preview"
},
"dependencies": {
"framer-motion": "^12.34.0",
"motion": "^12.34.0",
"motion-dom": "^12.34.0",
"framer-motion": "^12.34.1",
"motion": "^12.34.1",
"motion-dom": "^12.34.1",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
Expand Down
4 changes: 2 additions & 2 deletions dev/next/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "next-env",
"private": true,
"version": "12.34.0",
"version": "12.34.1",
"type": "module",
"scripts": {
"dev": "next dev",
Expand All @@ -10,7 +10,7 @@
"build": "next build"
},
"dependencies": {
"motion": "^12.34.0",
"motion": "^12.34.1",
"next": "15.5.10",
"react": "19.0.0",
"react-dom": "19.0.0"
Expand Down
4 changes: 2 additions & 2 deletions dev/react-19/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "react-19-env",
"private": true,
"version": "12.34.0",
"version": "12.34.1",
"type": "module",
"scripts": {
"dev": "vite",
Expand All @@ -11,7 +11,7 @@
"preview": "vite preview"
},
"dependencies": {
"motion": "^12.34.0",
"motion": "^12.34.1",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
Expand Down
4 changes: 2 additions & 2 deletions dev/react/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "react-env",
"private": true,
"version": "12.34.0",
"version": "12.34.1",
"type": "module",
"scripts": {
"dev": "yarn vite",
Expand All @@ -11,7 +11,7 @@
"preview": "yarn vite preview"
},
"dependencies": {
"framer-motion": "^12.34.0",
"framer-motion": "^12.34.1",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
Expand Down
44 changes: 44 additions & 0 deletions dev/react/src/tests/scroll-target-transform.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { motion, useScroll, useTransform } from "framer-motion"
import * as React from "react"
import { useRef } from "react"

export const App = () => {
const targetRef = useRef<HTMLDivElement>(null)
const { scrollYProgress } = useScroll({
target: targetRef,
offset: ["start end", "end start"],
})

const opacity = useTransform(scrollYProgress, [0, 1], [1, 0])
const y = useTransform(scrollYProgress, [0, 1], [0, -100])

return (
<>
<div style={spacer} />
<div ref={targetRef} style={targetStyle}>
<motion.div
id="target"
style={{ ...box, opacity, y }}
/>
</div>
<div style={spacer} />
<div style={spacer} />
<span id="has-accelerate">
{scrollYProgress.accelerate ? "true" : "false"}
</span>
</>
)
}

const spacer = { height: "100vh" }
const targetStyle: React.CSSProperties = {
height: "100vh",
display: "flex",
alignItems: "center",
justifyContent: "center",
}
const box: React.CSSProperties = {
width: 100,
height: 100,
background: "red",
}
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "12.34.0",
"version": "12.34.1",
"packages": [
"packages/*",
"dev/*"
Expand Down
10 changes: 8 additions & 2 deletions packages/framer-motion/cypress/integration/scroll-accelerate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ describe("scroll timeline WAAPI acceleration", () => {
.wait(200)
.get("#direct-accelerated")
.should(([$el]: any) => {
expect($el.innerText).to.equal("true")
const expected = (window as any).ScrollTimeline
? "true"
: "false"
expect($el.innerText).to.equal(expected)
})
})

Expand All @@ -16,7 +19,10 @@ describe("scroll timeline WAAPI acceleration", () => {
// backgroundColor gets accelerate config propagated,
// but VisualElement skips WAAPI creation since it's
// not in the acceleratedValues set
expect($el.innerText).to.equal("true")
const expected = (window as any).ScrollTimeline
? "true"
: "false"
expect($el.innerText).to.equal(expected)
})
})

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
describe("useScroll with target does not set accelerate", () => {
it("Does not set accelerate when target is provided", () => {
cy.visit("?test=scroll-target-transform")
.wait(200)
.get("#has-accelerate")
.should(([$el]: any) => {
expect($el.innerText).to.equal("false")
})
})

it("Opacity updates via useTransform when scrolling", () => {
cy.visit("?test=scroll-target-transform")
.wait(200)
.get("#target")
.should(([$el]: any) => {
// Before scrolling, opacity should be near initial value
const initialOpacity = parseFloat(
getComputedStyle($el).opacity
)
expect(initialOpacity).to.be.greaterThan(0)
})
cy.scrollTo("bottom", { duration: 300 })
.wait(200)
.get("#target")
.should(([$el]: any) => {
// After scrolling, opacity should have changed
const opacity = parseFloat(getComputedStyle($el).opacity)
expect(opacity).to.be.lessThan(1)
})
})
})
4 changes: 2 additions & 2 deletions packages/framer-motion/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "framer-motion",
"version": "12.34.0",
"version": "12.34.1",
"description": "A simple and powerful JavaScript animation library",
"main": "dist/cjs/index.js",
"module": "dist/es/index.mjs",
Expand Down Expand Up @@ -88,7 +88,7 @@
"measure": "rollup -c ./rollup.size.config.mjs"
},
"dependencies": {
"motion-dom": "^12.34.0",
"motion-dom": "^12.34.1",
"motion-utils": "^12.29.2",
"tslib": "^2.4.0"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { supportsScrollTimeline } from "motion-dom"

export function canUseNativeTimeline(target?: Element) {
return (
typeof window !== "undefined" && !target && supportsScrollTimeline()
)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ProgressTimeline, supportsScrollTimeline } from "motion-dom"
import { ProgressTimeline } from "motion-dom"
import { scrollInfo } from "../track"
import { ScrollOptionsWithDefaults } from "../types"
import { canUseNativeTimeline } from "./can-use-native-timeline"

declare global {
interface Window {
Expand Down Expand Up @@ -50,7 +51,7 @@ export function getTimeline({

if (!targetCache[axisKey]) {
targetCache[axisKey] =
!options.target && supportsScrollTimeline()
canUseNativeTimeline(options.target)
? new ScrollTimeline({ source: container, axis } as any)
: scrollTimelineFallback({ container, ...options })
}
Expand Down
88 changes: 88 additions & 0 deletions packages/framer-motion/src/value/__tests__/use-scroll.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { supportsFlags } from "motion-dom"
import { useRef } from "react"
import { render } from "../../jest.setup"
import { useScroll } from "../use-scroll"
import { useTransform } from "../use-transform"

describe("useScroll accelerate", () => {
afterEach(() => {
supportsFlags.scrollTimeline = undefined
})

test("sets accelerate on progress values when ScrollTimeline is supported and no target", () => {
supportsFlags.scrollTimeline = true

let accelerateX: any
let accelerateY: any

const Component = () => {
const { scrollXProgress, scrollYProgress } = useScroll()
accelerateX = scrollXProgress.accelerate
accelerateY = scrollYProgress.accelerate
return null
}

render(<Component />)

expect(accelerateX).toBeDefined()
expect(accelerateY).toBeDefined()
})

test("does not set accelerate when target ref is provided", () => {
supportsFlags.scrollTimeline = true

let accelerateX: any
let accelerateY: any

const Component = () => {
const target = useRef<HTMLDivElement>(null)
const { scrollXProgress, scrollYProgress } = useScroll({
target,
})
accelerateX = scrollXProgress.accelerate
accelerateY = scrollYProgress.accelerate
return <div ref={target} />
}

render(<Component />)

expect(accelerateX).toBeUndefined()
expect(accelerateY).toBeUndefined()
})

test("does not set accelerate when ScrollTimeline is not supported", () => {
supportsFlags.scrollTimeline = false

let accelerateX: any
let accelerateY: any

const Component = () => {
const { scrollXProgress, scrollYProgress } = useScroll()
accelerateX = scrollXProgress.accelerate
accelerateY = scrollYProgress.accelerate
return null
}

render(<Component />)

expect(accelerateX).toBeUndefined()
expect(accelerateY).toBeUndefined()
})

test("propagates accelerate through useTransform", () => {
supportsFlags.scrollTimeline = true

let transformAccelerate: any

const Component = () => {
const { scrollYProgress } = useScroll()
const opacity = useTransform(scrollYProgress, [0, 1], [0, 1])
transformAccelerate = opacity.accelerate
return null
}

render(<Component />)

expect(transformAccelerate).toBeDefined()
})
})
Loading
Loading